mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-29 06:06:52 +08:00
feat(react,vue): improve managing node performance
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
|
||||
# 事件监听器
|
||||
|
||||
这里是向前端发送一个名叫 rotate 的事件,里面有个参数是 result,这样就发送到前端去了。
|
||||
这里监听 rotate 的事件,里面有回调参数 result。
|
||||
|
||||
```js
|
||||
// 将入口文件中 setApp() 时保存的 Vue 实例取出来。
|
||||
@@ -18,7 +18,7 @@ export default {
|
||||
console.log(rsp.result);
|
||||
}
|
||||
},
|
||||
didMount() {
|
||||
mounted() {
|
||||
// 通过 app 监听 rotate 事件,并通过 this.listener 在事件发生时触发回调。
|
||||
app.$on('rotate', this.listener);
|
||||
}
|
||||
@@ -26,11 +26,21 @@ export default {
|
||||
|
||||
```
|
||||
|
||||
# 事件触发
|
||||
|
||||
如果需要手动发送事件,可以通过 `app.$emit` 触发。
|
||||
|
||||
```js
|
||||
const app = getApp();
|
||||
app.$emit('rotate', { width: 100, height: 100 });
|
||||
```
|
||||
|
||||
# 事件卸载
|
||||
|
||||
如果不需要使用的时候记得调用一下移除监听的方法,一般放在组件的卸载生命周期中执行。
|
||||
|
||||
```jsx
|
||||
```js
|
||||
const app = getApp();
|
||||
app.$off('rotate', this.listener);
|
||||
```
|
||||
|
||||
|
||||
@@ -42,10 +42,6 @@ class ViewNode {
|
||||
|
||||
public parentNode: ViewNode | null = null;
|
||||
|
||||
public prevSibling: ViewNode | null = null;
|
||||
|
||||
public nextSibling: ViewNode | null = null;
|
||||
|
||||
constructor() {
|
||||
// Virtual DOM node id, will used in native to identify.
|
||||
this.nodeId = getNodeId();
|
||||
@@ -56,16 +52,6 @@ class ViewNode {
|
||||
return this.constructor.name;
|
||||
}
|
||||
|
||||
get firstChild() {
|
||||
return this.childNodes.length ? this.childNodes[0] : null;
|
||||
}
|
||||
|
||||
get lastChild() {
|
||||
return this.childNodes.length
|
||||
? this.childNodes[this.childNodes.length - 1]
|
||||
: null;
|
||||
}
|
||||
|
||||
get isMounted() {
|
||||
return this.mounted;
|
||||
}
|
||||
@@ -79,104 +65,66 @@ class ViewNode {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t insert child.');
|
||||
}
|
||||
|
||||
if (childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
if (!referenceNode) {
|
||||
return this.appendChild(childNode);
|
||||
}
|
||||
|
||||
if (referenceNode.parentNode !== this) {
|
||||
throw new Error('Can\'t insert child, because the reference node has a different parent.');
|
||||
}
|
||||
|
||||
if (childNode.parentNode && childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t insert child, because it already has a different parent.');
|
||||
}
|
||||
|
||||
const index = this.childNodes.indexOf(referenceNode);
|
||||
|
||||
childNode.parentNode = this;
|
||||
childNode.nextSibling = referenceNode;
|
||||
childNode.prevSibling = this.childNodes[index - 1];
|
||||
|
||||
referenceNode.prevSibling = childNode;
|
||||
this.childNodes.splice(index, 0, childNode);
|
||||
|
||||
return insertChild(this, childNode, index);
|
||||
}
|
||||
|
||||
moveChild(childNode: ViewNode, referenceNode: ViewNode) {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t mvoe child.');
|
||||
throw new Error('Can\'t move child.');
|
||||
}
|
||||
if (childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!referenceNode) {
|
||||
return this.appendChild(childNode);
|
||||
}
|
||||
|
||||
if (referenceNode.parentNode !== this) {
|
||||
throw new Error('Can\'t move child, because the reference node has a different parent.');
|
||||
}
|
||||
|
||||
if (childNode.parentNode && childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t move child, because it already has a different parent.');
|
||||
}
|
||||
|
||||
const oldIndex = this.childNodes.indexOf(childNode);
|
||||
const newIndex = this.childNodes.indexOf(referenceNode);
|
||||
|
||||
const referenceIndex = this.childNodes.indexOf(referenceNode);
|
||||
// return if the moved index is the same as the previous one
|
||||
if (newIndex === oldIndex) {
|
||||
if (referenceIndex === oldIndex) {
|
||||
return childNode;
|
||||
}
|
||||
|
||||
// set new siblings relations
|
||||
childNode.nextSibling = referenceNode;
|
||||
childNode.prevSibling = referenceNode.prevSibling;
|
||||
referenceNode.prevSibling = childNode;
|
||||
|
||||
if (this.childNodes[newIndex - 1]) {
|
||||
this.childNodes[newIndex - 1].nextSibling = childNode;
|
||||
}
|
||||
if (this.childNodes[newIndex + 1]) {
|
||||
this.childNodes[newIndex + 1].prevSibling = childNode;
|
||||
}
|
||||
if (this.childNodes[oldIndex - 1]) {
|
||||
this.childNodes[oldIndex - 1].nextSibling = this.childNodes[oldIndex + 1];
|
||||
}
|
||||
if (this.childNodes[oldIndex + 1]) {
|
||||
this.childNodes[oldIndex + 1].prevSibling = this.childNodes[oldIndex - 1];
|
||||
}
|
||||
|
||||
// remove old child node from native
|
||||
removeChild(this, childNode);
|
||||
|
||||
// remove old child and insert new child, which is like moving child
|
||||
this.childNodes.splice(oldIndex, 1);
|
||||
removeChild(this, childNode, oldIndex);
|
||||
const newIndex = this.childNodes.indexOf(referenceNode);
|
||||
this.childNodes.splice(newIndex, 0, childNode);
|
||||
this.childNodes.splice(oldIndex + (newIndex < oldIndex ? 1 : 0), 1);
|
||||
|
||||
// should filter empty nodes before finding the index of node
|
||||
const atIndex = this.childNodes.filter(ch => ch.index > -1).indexOf(childNode);
|
||||
return insertChild(this, childNode, atIndex);
|
||||
return insertChild(this, childNode, newIndex);
|
||||
}
|
||||
|
||||
appendChild(childNode: ViewNode) {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t append child.');
|
||||
}
|
||||
|
||||
if (childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
if (childNode.parentNode && childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t append child, because it already has a different parent.');
|
||||
}
|
||||
|
||||
childNode.parentNode = this;
|
||||
|
||||
if (this.lastChild) {
|
||||
childNode.prevSibling = this.lastChild;
|
||||
this.lastChild.nextSibling = childNode;
|
||||
}
|
||||
|
||||
this.childNodes.push(childNode);
|
||||
|
||||
insertChild(this, childNode, this.childNodes.length - 1);
|
||||
}
|
||||
|
||||
@@ -184,36 +132,18 @@ class ViewNode {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t remove child.');
|
||||
}
|
||||
|
||||
if (!childNode.parentNode) {
|
||||
throw new Error('Can\'t remove child, because it has no parent.');
|
||||
}
|
||||
|
||||
if (childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t remove child, because it has a different parent.');
|
||||
}
|
||||
|
||||
if (childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeChild(this, childNode);
|
||||
|
||||
// FIXME: parentNode should be null when removeChild, But it breaks add the node again.
|
||||
// Issue position: https://github.com/vuejs/vue/tree/master/src/core/vdom/patch.js#L250
|
||||
// childNode.parentNode = null;
|
||||
|
||||
if (childNode.prevSibling) {
|
||||
childNode.prevSibling.nextSibling = childNode.nextSibling;
|
||||
childNode.prevSibling = null;
|
||||
if (!childNode.parentNode) {
|
||||
throw new Error('Can\'t remove child, because it has no parent.');
|
||||
}
|
||||
|
||||
if (childNode.nextSibling) {
|
||||
childNode.nextSibling.prevSibling = childNode.prevSibling;
|
||||
childNode.nextSibling = null;
|
||||
if (childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t remove child, because it has a different parent.');
|
||||
}
|
||||
|
||||
this.childNodes = this.childNodes.filter(node => node !== childNode);
|
||||
const index = this.childNodes.indexOf(childNode);
|
||||
this.childNodes.splice(index, 1);
|
||||
removeChild(this, childNode, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -238,22 +168,16 @@ class ViewNode {
|
||||
|
||||
/**
|
||||
* Traverse the children and execute callback
|
||||
* @param callback - callback function
|
||||
* @param newIndex - index to be updated
|
||||
*/
|
||||
traverseChildren(callback: Function) {
|
||||
// Find the index and apply callback
|
||||
let index;
|
||||
if (this.parentNode) {
|
||||
index = this.parentNode.childNodes.filter(node => !node.meta.skipAddToDom).indexOf(this);
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
this.index = index;
|
||||
traverseChildren(callback: Function, newIndex: number | undefined = 0) {
|
||||
this.index = !this.parentNode ? 0 : newIndex;
|
||||
callback(this);
|
||||
|
||||
// Find the children
|
||||
if (this.childNodes.length) {
|
||||
this.childNodes.forEach((childNode) => {
|
||||
this.traverseChildren.call(childNode, callback);
|
||||
this.childNodes.forEach((childNode, index) => {
|
||||
this.traverseChildren.call(childNode, callback, index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ class HippyReact implements HippyReact {
|
||||
const { __instanceId__: rootViewId } = superProps;
|
||||
trace(...componentName, 'Start', appName, 'with rootViewId', rootViewId, superProps);
|
||||
|
||||
// Update nodeId for contaienr
|
||||
// Update nodeId for container
|
||||
this.rootContainer.containerInfo.nodeId = rootViewId;
|
||||
if (silent) {
|
||||
setSilent(silent);
|
||||
|
||||
@@ -59,6 +59,7 @@ function createInstance(
|
||||
}
|
||||
}
|
||||
});
|
||||
// only HostComponent(5) or Fragment(7) render to native
|
||||
if ([5, 7].indexOf(workInProgress.tag) < 0) {
|
||||
element.meta.skipAddToDom = true;
|
||||
}
|
||||
@@ -71,7 +72,6 @@ function createTextInstance(newText: string, rootContainerInstance: Document) {
|
||||
element.meta = {
|
||||
component: {
|
||||
name: 'Text',
|
||||
skipAddToDom: true,
|
||||
},
|
||||
};
|
||||
return element;
|
||||
|
||||
@@ -113,7 +113,7 @@ function getNativeProps(node: Element) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get target node attributes, use to chrome devTool tag attribute show while debugging
|
||||
* Get target node attributes, used to chrome devTool tag attribute show while debugging
|
||||
*/
|
||||
function getTargetNodeAttributes(targetNode: Element) {
|
||||
try {
|
||||
@@ -124,7 +124,6 @@ function getTargetNodeAttributes(targetNode: Element) {
|
||||
};
|
||||
delete attributes.text;
|
||||
delete attributes.value;
|
||||
|
||||
return attributes;
|
||||
} catch (e) {
|
||||
warn('getTargetNodeAttributes error:', e);
|
||||
@@ -140,14 +139,12 @@ function renderToNative(rootViewId: number, targetNode: Element): Hippy.NativeNo
|
||||
warn('Component need to define the native name', targetNode);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (targetNode.meta.skipAddToDom) {
|
||||
return null;
|
||||
}
|
||||
if (!targetNode.meta.component) {
|
||||
throw new Error(`Specific tag is not supported yet: ${targetNode.tagName}`);
|
||||
}
|
||||
|
||||
// Translate to native node
|
||||
const nativeNode: Hippy.NativeNode = {
|
||||
id: targetNode.nodeId,
|
||||
@@ -171,15 +168,22 @@ function renderToNative(rootViewId: number, targetNode: Element): Hippy.NativeNo
|
||||
|
||||
/**
|
||||
* Render Element with child to native
|
||||
* @param rootViewId - rootView id
|
||||
* @param node - current node
|
||||
* @param atIndex - current node index
|
||||
*/
|
||||
function renderToNativeWithChildren(rootViewId: number, node: ViewNode) {
|
||||
function renderToNativeWithChildren(rootViewId: number, node: ViewNode, atIndex?: number | undefined) {
|
||||
const nativeLanguages: Hippy.NativeNode[] = [];
|
||||
let index = atIndex;
|
||||
if (typeof index === 'undefined' && node && node.parentNode) {
|
||||
index = node.parentNode.childNodes.indexOf(node);
|
||||
}
|
||||
node.traverseChildren((targetNode: Element) => {
|
||||
const nativeNode = renderToNative(rootViewId, targetNode);
|
||||
if (nativeNode) {
|
||||
nativeLanguages.push(nativeNode);
|
||||
}
|
||||
});
|
||||
}, index);
|
||||
return nativeLanguages;
|
||||
}
|
||||
|
||||
@@ -194,19 +198,17 @@ function isLayout(node: ViewNode) {
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||
function insertChild(parentNode: ViewNode, childNode: ViewNode, atIndex = -1) {
|
||||
if (!parentNode) {
|
||||
if (!parentNode || !childNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rootViewId = getRootViewId();
|
||||
// Render the root node
|
||||
if (isLayout(parentNode) && !parentNode.isMounted) {
|
||||
// Start real native work.
|
||||
const translated = renderToNativeWithChildren(rootViewId, childNode);
|
||||
const translated = renderToNativeWithChildren(rootViewId, childNode, atIndex);
|
||||
startBatch();
|
||||
batchNodes.push({
|
||||
type: NODE_OPERATION_TYPES.createNode,
|
||||
@@ -217,10 +219,10 @@ function insertChild(parentNode: ViewNode, childNode: ViewNode, atIndex = -1) {
|
||||
if (!node.isMounted) {
|
||||
node.isMounted = true;
|
||||
}
|
||||
});
|
||||
// Render others child nodes.
|
||||
}, atIndex);
|
||||
// Render others child nodes.
|
||||
} else if (parentNode.isMounted && !childNode.isMounted) {
|
||||
const translated = renderToNativeWithChildren(rootViewId, childNode);
|
||||
const translated = renderToNativeWithChildren(rootViewId, childNode, atIndex);
|
||||
startBatch();
|
||||
batchNodes.push({
|
||||
type: NODE_OPERATION_TYPES.createNode,
|
||||
@@ -231,20 +233,16 @@ function insertChild(parentNode: ViewNode, childNode: ViewNode, atIndex = -1) {
|
||||
if (!node.isMounted) {
|
||||
node.isMounted = true;
|
||||
}
|
||||
});
|
||||
}, atIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function removeChild(parentNode: ViewNode, childNode: ViewNode) {
|
||||
function removeChild(parentNode: ViewNode, childNode: ViewNode | null, index: number) {
|
||||
if (!childNode || childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
childNode.isMounted = false;
|
||||
childNode.traverseChildren((targetNode: ViewNode) => {
|
||||
if (targetNode.isMounted) {
|
||||
targetNode.isMounted = false;
|
||||
}
|
||||
});
|
||||
childNode.index = index;
|
||||
const rootViewId = getRootViewId();
|
||||
const deleteNodeIds: Hippy.NativeNode[] = [{
|
||||
id: childNode.nodeId,
|
||||
|
||||
@@ -71,7 +71,7 @@ function getLinearGradientAngle(value) {
|
||||
const reg = /^([+-]?\d+\.?\d*)+(deg|turn|rad)|(to\w+)$/g;
|
||||
const valueList = reg.exec(processedValue);
|
||||
if (!Array.isArray(valueList)) return;
|
||||
// default direction is to bottom, i.e. 180degree
|
||||
// if default direction is to bottom, i.e. 180degree
|
||||
let angle = '180';
|
||||
const [direction, angleValue, angleUnit] = valueList;
|
||||
if (angleValue && angleUnit) { // angle value
|
||||
@@ -172,28 +172,20 @@ function parseTextShadowOffset(property, value = 0, style) {
|
||||
class ElementNode extends ViewNode {
|
||||
constructor(tagName) {
|
||||
super();
|
||||
|
||||
// Tag name
|
||||
this.tagName = tagName;
|
||||
|
||||
// ID attribute in template.
|
||||
this.id = '';
|
||||
|
||||
// style attribute in template.
|
||||
this.style = {};
|
||||
|
||||
// Vue style scope id.
|
||||
this._styleScopeId = null;
|
||||
|
||||
// Class attribute in template.
|
||||
this.classList = new Set(); // Fake DOMTokenLis
|
||||
|
||||
// Other attributes in template.
|
||||
this.attributes = {};
|
||||
|
||||
// Event observer.
|
||||
this._emitter = null;
|
||||
|
||||
// Style pre-processor
|
||||
this.beforeLoadStyle = getBeforeLoadStyle();
|
||||
}
|
||||
@@ -242,7 +234,6 @@ class ElementNode extends ViewNode {
|
||||
updateChild(this);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'class': {
|
||||
const newClassList = new Set(value.split(' ').filter(x => x.trim()));
|
||||
@@ -293,7 +284,6 @@ class ElementNode extends ViewNode {
|
||||
default:
|
||||
this.attributes[key] = value;
|
||||
}
|
||||
|
||||
updateChild(this);
|
||||
} catch (err) {
|
||||
// Throw error in development mode
|
||||
@@ -312,7 +302,6 @@ class ElementNode extends ViewNode {
|
||||
delete this.style[property];
|
||||
return;
|
||||
}
|
||||
|
||||
// Preprocess the style
|
||||
let {
|
||||
property: p,
|
||||
@@ -321,8 +310,7 @@ class ElementNode extends ViewNode {
|
||||
property,
|
||||
value,
|
||||
});
|
||||
|
||||
// Process the specifc style value
|
||||
// Process the specific style value
|
||||
switch (p) {
|
||||
case 'fontWeight':
|
||||
if (typeof v !== 'string') {
|
||||
@@ -366,7 +354,6 @@ class ElementNode extends ViewNode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (v === undefined || v === null || this.style[p] === v) {
|
||||
return;
|
||||
}
|
||||
@@ -400,7 +387,6 @@ class ElementNode extends ViewNode {
|
||||
|
||||
appendChild(childNode) {
|
||||
super.appendChild(childNode);
|
||||
|
||||
if (childNode.meta.symbol === Text) {
|
||||
this.setText(childNode.text);
|
||||
}
|
||||
@@ -408,7 +394,6 @@ class ElementNode extends ViewNode {
|
||||
|
||||
insertBefore(childNode, referenceNode) {
|
||||
super.insertBefore(childNode, referenceNode);
|
||||
|
||||
if (childNode.meta.symbol === Text) {
|
||||
this.setText(childNode.text);
|
||||
}
|
||||
@@ -416,7 +401,6 @@ class ElementNode extends ViewNode {
|
||||
|
||||
moveChild(childNode, referenceNode) {
|
||||
super.moveChild(childNode, referenceNode);
|
||||
|
||||
if (childNode.meta.symbol === Text) {
|
||||
this.setText(childNode.text);
|
||||
}
|
||||
@@ -424,7 +408,6 @@ class ElementNode extends ViewNode {
|
||||
|
||||
removeChild(childNode) {
|
||||
super.removeChild(childNode);
|
||||
|
||||
if (childNode.meta.symbol === Text) {
|
||||
this.setText('');
|
||||
}
|
||||
|
||||
@@ -122,13 +122,17 @@ function endBatch(app) {
|
||||
}
|
||||
|
||||
function getCssMap() {
|
||||
// To support dynamic import. __cssMap can be loaded from differnet js file.
|
||||
// __cssMap should be create/append if global[GLOBAL_STYLE_NAME] exists;
|
||||
/**
|
||||
* To support dynamic import, __cssMap can be loaded from different js file.
|
||||
* __cssMap should be create/append if global[GLOBAL_STYLE_NAME] exists;
|
||||
*/
|
||||
if (__cssMap && !global[GLOBAL_STYLE_NAME]) {
|
||||
return __cssMap;
|
||||
}
|
||||
// HERE IS A SECRET STARTUP OPTION: beforeStyleLoadHook
|
||||
// Usage for process the styles while styles loading.
|
||||
/**
|
||||
* Here is a secret startup option: beforeStyleLoadHook.
|
||||
* Usage for process the styles while styles loading.
|
||||
*/
|
||||
const cssRules = fromAstNodes(global[GLOBAL_STYLE_NAME]);
|
||||
if (__cssMap) {
|
||||
__cssMap.append(cssRules);
|
||||
@@ -145,7 +149,6 @@ function getCssMap() {
|
||||
function getNativeProps(node) {
|
||||
// Initial the props with empty
|
||||
const props = {};
|
||||
|
||||
// Get the default native props from meta
|
||||
if (node.meta.component.defaultNativeProps) {
|
||||
Object.keys(node.meta.component.defaultNativeProps).forEach((key) => {
|
||||
@@ -162,36 +165,31 @@ function getNativeProps(node) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Get the proceed props from node attributes
|
||||
Object.keys(node.attributes).forEach((key) => {
|
||||
let value = node.getAttribute(key);
|
||||
|
||||
// No defined map
|
||||
if (!node.meta.component.attributeMaps || !node.meta.component.attributeMaps[key]) {
|
||||
props[key] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Defined mapped props.
|
||||
const map = node.meta.component.attributeMaps[key];
|
||||
if (typeof map === 'string') {
|
||||
props[map] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Define mapped props is a function.
|
||||
if (isFunction(map)) {
|
||||
props[key] = map(value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Defined object map with value
|
||||
const { name: propsKey, propsValue, jointKey } = map;
|
||||
if (isFunction(propsValue)) {
|
||||
value = propsValue(value);
|
||||
}
|
||||
// if jointKey set, multi attributes will be assigned to the same jointKey object
|
||||
// if jointKey set, multi attributes will be assigned to the same jointKey object.
|
||||
if (jointKey) {
|
||||
props[jointKey] = props[jointKey] || {};
|
||||
Object.assign(props[jointKey], {
|
||||
@@ -274,7 +272,7 @@ function parseViewComponent(targetNode, nativeNode, style) {
|
||||
/**
|
||||
* Get target node attributes, use to chrome devTool tag attribute show while debugging
|
||||
* @param targetNode
|
||||
* @returns attributes
|
||||
* @returns attributes|{}
|
||||
*/
|
||||
function getTargetNodeAttributes(targetNode) {
|
||||
try {
|
||||
@@ -305,13 +303,11 @@ function renderToNative(rootViewId, targetNode) {
|
||||
if (!targetNode.meta.component) {
|
||||
throw new Error(`Specific tag is not supported yet: ${targetNode.tagName}`);
|
||||
}
|
||||
|
||||
let style = {};
|
||||
// Apply styles when the targetNode attach to document at first time.
|
||||
if (targetNode.meta.component.defaultNativeStyle) {
|
||||
style = { ...targetNode.meta.component.defaultNativeStyle };
|
||||
}
|
||||
|
||||
// Apply styles from CSS
|
||||
const matchedSelectors = getCssMap().query(targetNode);
|
||||
matchedSelectors.selectors.forEach((matchedSelector) => {
|
||||
@@ -319,10 +315,8 @@ function renderToNative(rootViewId, targetNode) {
|
||||
style[cssStyle.property] = cssStyle.value;
|
||||
});
|
||||
});
|
||||
|
||||
// Apply style from style attribute.
|
||||
style = { ...style, ...targetNode.style };
|
||||
|
||||
// Convert to real native event
|
||||
const events = {};
|
||||
// FIXME: Bad accessing the private property.
|
||||
@@ -344,7 +338,6 @@ function renderToNative(rootViewId, targetNode) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Translate to native node
|
||||
const nativeNode = {
|
||||
id: targetNode.nodeId,
|
||||
@@ -357,12 +350,11 @@ function renderToNative(rootViewId, targetNode) {
|
||||
style,
|
||||
},
|
||||
};
|
||||
// Add nativeNode attributes info for debugging
|
||||
// Add nativeNode attributes info for Element debugging
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
nativeNode.tagName = targetNode.tagName;
|
||||
nativeNode.props.attributes = getTargetNodeAttributes(targetNode);
|
||||
}
|
||||
|
||||
parseViewComponent(targetNode, nativeNode, style);
|
||||
parseTextInputComponent(targetNode, style);
|
||||
return nativeNode;
|
||||
@@ -400,18 +392,15 @@ function isLayout(node, rootView) {
|
||||
}
|
||||
|
||||
function insertChild(parentNode, childNode, atIndex = -1) {
|
||||
if (!parentNode) {
|
||||
if (!parentNode || !childNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (parentNode.meta && isFunction(parentNode.meta.insertChild)) {
|
||||
parentNode.meta.insertChild(parentNode, childNode, atIndex);
|
||||
}
|
||||
|
||||
if (childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
const app = getApp();
|
||||
if (!app) {
|
||||
return;
|
||||
@@ -449,33 +438,22 @@ function insertChild(parentNode, childNode, atIndex = -1) {
|
||||
}
|
||||
}
|
||||
|
||||
function removeChild(parentNode, childNode) {
|
||||
function removeChild(parentNode, childNode, index) {
|
||||
if (parentNode && parentNode.meta && isFunction(parentNode.meta.removeChild)) {
|
||||
parentNode.meta.removeChild(parentNode, childNode);
|
||||
}
|
||||
|
||||
if (!childNode || childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
childNode.isMounted = false;
|
||||
childNode.traverseChildren((node) => {
|
||||
if (node.isMounted) {
|
||||
node.isMounted = false;
|
||||
}
|
||||
});
|
||||
childNode.index = index;
|
||||
const app = getApp();
|
||||
const deleteNodeIds = [];
|
||||
childNode.traverseChildren((targetNode) => {
|
||||
if (targetNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
deleteNodeIds.push({
|
||||
id: targetNode.nodeId,
|
||||
index: targetNode.index,
|
||||
pId: targetNode.parentNode.nodeId,
|
||||
});
|
||||
});
|
||||
const { $options: { rootViewId } } = app;
|
||||
const deleteNodeIds = [{
|
||||
id: childNode.nodeId,
|
||||
pId: childNode.parentNode ? childNode.parentNode.nodeId : rootViewId,
|
||||
index: childNode.index,
|
||||
}];
|
||||
startBatch();
|
||||
__batchNodes.push({
|
||||
type: NODE_OPERATION_TYPES.deleteNode,
|
||||
|
||||
@@ -95,33 +95,25 @@ class ViewNode {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t insert child.');
|
||||
}
|
||||
|
||||
if (!referenceNode) {
|
||||
return this.appendChild(childNode);
|
||||
}
|
||||
|
||||
if (referenceNode.parentNode !== this) {
|
||||
throw new Error('Can\'t insert child, because the reference node has a different parent.');
|
||||
}
|
||||
|
||||
if (childNode.parentNode && childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t insert child, because it already has a different parent.');
|
||||
}
|
||||
|
||||
const index = this.childNodes.indexOf(referenceNode);
|
||||
|
||||
childNode.parentNode = this;
|
||||
childNode.nextSibling = referenceNode;
|
||||
childNode.prevSibling = this.childNodes[index - 1];
|
||||
|
||||
// update previous node's nextSibling to prevent patch bug
|
||||
if (this.childNodes[index - 1]) {
|
||||
this.childNodes[index - 1].nextSibling = childNode;
|
||||
}
|
||||
|
||||
referenceNode.prevSibling = childNode;
|
||||
this.childNodes.splice(index, 0, childNode);
|
||||
|
||||
return insertChild(this, childNode, index);
|
||||
}
|
||||
|
||||
@@ -129,37 +121,30 @@ class ViewNode {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t move child.');
|
||||
}
|
||||
|
||||
if (!referenceNode) {
|
||||
return this.appendChild(childNode);
|
||||
}
|
||||
|
||||
if (referenceNode.parentNode !== this) {
|
||||
throw new Error('Can\'t move child, because the reference node has a different parent.');
|
||||
}
|
||||
|
||||
if (childNode.parentNode && childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t move child, because it already has a different parent.');
|
||||
}
|
||||
|
||||
const oldIndex = this.childNodes.indexOf(childNode);
|
||||
const newIndex = this.childNodes.indexOf(referenceNode);
|
||||
|
||||
const referenceIndex = this.childNodes.indexOf(referenceNode);
|
||||
// return if the moved index is the same as the previous one
|
||||
if (newIndex === oldIndex) {
|
||||
if (referenceIndex === oldIndex) {
|
||||
return childNode;
|
||||
}
|
||||
|
||||
// set new siblings relations
|
||||
childNode.nextSibling = referenceNode;
|
||||
childNode.prevSibling = referenceNode.prevSibling;
|
||||
referenceNode.prevSibling = childNode;
|
||||
|
||||
if (this.childNodes[newIndex - 1]) {
|
||||
this.childNodes[newIndex - 1].nextSibling = childNode;
|
||||
if (this.childNodes[referenceIndex - 1]) {
|
||||
this.childNodes[referenceIndex - 1].nextSibling = childNode;
|
||||
}
|
||||
if (this.childNodes[newIndex + 1]) {
|
||||
this.childNodes[newIndex + 1].prevSibling = childNode;
|
||||
if (this.childNodes[referenceIndex + 1]) {
|
||||
this.childNodes[referenceIndex + 1].prevSibling = childNode;
|
||||
}
|
||||
if (this.childNodes[oldIndex - 1]) {
|
||||
this.childNodes[oldIndex - 1].nextSibling = this.childNodes[oldIndex + 1];
|
||||
@@ -167,42 +152,31 @@ class ViewNode {
|
||||
if (this.childNodes[oldIndex + 1]) {
|
||||
this.childNodes[oldIndex + 1].prevSibling = this.childNodes[oldIndex - 1];
|
||||
}
|
||||
|
||||
this.childNodes.splice(oldIndex, 1);
|
||||
// remove old child node from native
|
||||
removeChild(this, childNode);
|
||||
|
||||
// remove old child and insert new child, which is like moving child
|
||||
removeChild(this, childNode, oldIndex);
|
||||
const newIndex = this.childNodes.indexOf(referenceNode);
|
||||
this.childNodes.splice(newIndex, 0, childNode);
|
||||
this.childNodes.splice(oldIndex + (newIndex < oldIndex ? 1 : 0), 1);
|
||||
|
||||
// should filter empty nodes before finding the index of node
|
||||
const atIndex = this.childNodes.filter(ch => ch.index > -1).indexOf(childNode);
|
||||
return insertChild(this, childNode, atIndex);
|
||||
return insertChild(this, childNode, newIndex);
|
||||
}
|
||||
|
||||
appendChild(childNode) {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t append child.');
|
||||
}
|
||||
|
||||
if (childNode.parentNode && childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t append child, because it already has a different parent.');
|
||||
}
|
||||
|
||||
// remove childNode if exist
|
||||
if (childNode.isMounted) {
|
||||
this.removeChild(childNode);
|
||||
}
|
||||
|
||||
childNode.parentNode = this;
|
||||
|
||||
if (this.lastChild) {
|
||||
childNode.prevSibling = this.lastChild;
|
||||
this.lastChild.nextSibling = childNode;
|
||||
}
|
||||
|
||||
this.childNodes.push(childNode);
|
||||
|
||||
insertChild(this, childNode, this.childNodes.length - 1);
|
||||
}
|
||||
|
||||
@@ -210,36 +184,29 @@ class ViewNode {
|
||||
if (!childNode) {
|
||||
throw new Error('Can\'t remove child.');
|
||||
}
|
||||
|
||||
if (!childNode.parentNode) {
|
||||
throw new Error('Can\'t remove child, because it has no parent.');
|
||||
}
|
||||
|
||||
if (childNode.parentNode !== this) {
|
||||
throw new Error('Can\'t remove child, because it has a different parent.');
|
||||
}
|
||||
|
||||
if (childNode.meta.skipAddToDom) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeChild(this, childNode);
|
||||
|
||||
// FIXME: parentNode should be null when removeChild, But it breaks add the node again.
|
||||
// Issue position: https://github.com/vuejs/vue/tree/master/src/core/vdom/patch.js#L250
|
||||
// childNode.parentNode = null;
|
||||
|
||||
if (childNode.prevSibling) {
|
||||
childNode.prevSibling.nextSibling = childNode.nextSibling;
|
||||
}
|
||||
|
||||
if (childNode.nextSibling) {
|
||||
childNode.nextSibling.prevSibling = childNode.prevSibling;
|
||||
}
|
||||
|
||||
childNode.prevSibling = null;
|
||||
childNode.nextSibling = null;
|
||||
this.childNodes = this.childNodes.filter(node => node !== childNode);
|
||||
const index = this.childNodes.indexOf(childNode);
|
||||
this.childNodes.splice(index, 1);
|
||||
removeChild(this, childNode, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -275,7 +242,6 @@ class ViewNode {
|
||||
}
|
||||
this.index = index;
|
||||
callback(this);
|
||||
|
||||
// Find the children
|
||||
if (this.childNodes.length) {
|
||||
this.childNodes.forEach((childNode) => {
|
||||
|
||||
@@ -91,7 +91,8 @@ Vue.prototype.$mount = function $mount(el, hydrating) {
|
||||
/**
|
||||
* Register the Hippy-Vue app to Native.
|
||||
*
|
||||
* @param {function} callback - Callback after register completed.
|
||||
* @param {function} afterCallback - Callback after register completed.
|
||||
* @param {function} beforeCallback - Callback before register completed.
|
||||
*/
|
||||
Vue.prototype.$start = function $start(afterCallback, beforeCallback) {
|
||||
setApp(this);
|
||||
@@ -108,15 +109,13 @@ Vue.prototype.$start = function $start(afterCallback, beforeCallback) {
|
||||
});
|
||||
|
||||
// Register the entry point into Hippy
|
||||
// The callback will be exectue when Native trigger loadInstance
|
||||
// The callback will be execute when Native trigger loadInstance
|
||||
// or runApplication event.
|
||||
HippyRegister.regist(this.$options.appName, (superProps) => {
|
||||
const { __instanceId__: rootViewId } = superProps;
|
||||
this.$options.$superProps = superProps;
|
||||
this.$options.rootViewId = rootViewId;
|
||||
|
||||
trace(...componentName, 'Start', this.$options.appName, 'with rootViewId', rootViewId, superProps);
|
||||
|
||||
// Destroy the old instance and set the new one when restart the app
|
||||
if (this.$el) {
|
||||
this.$destroy();
|
||||
@@ -124,15 +123,12 @@ Vue.prototype.$start = function $start(afterCallback, beforeCallback) {
|
||||
const newApp = new AppConstructor(this.$options);
|
||||
setApp(newApp);
|
||||
}
|
||||
|
||||
// Call the callback before $mount
|
||||
if (isFunction(beforeCallback)) {
|
||||
beforeCallback(this, superProps);
|
||||
}
|
||||
|
||||
// Draw the app.
|
||||
this.$mount();
|
||||
|
||||
// Draw the iPhone status bar background.
|
||||
// It should execute after $mount, otherwise this.$el will be undefined.
|
||||
if (Native.Platform === 'ios') {
|
||||
@@ -145,7 +141,6 @@ Vue.prototype.$start = function $start(afterCallback, beforeCallback) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call the callback after $mount
|
||||
if (isFunction(afterCallback)) {
|
||||
afterCallback(this, superProps);
|
||||
@@ -188,9 +183,7 @@ Vue.extend = function hippyExtend(extendOptions) {
|
||||
if (cachedCtors[SuperId]) {
|
||||
return cachedCtors[SuperId];
|
||||
}
|
||||
|
||||
const name = extendOptions.name || Super.options.name;
|
||||
|
||||
const Sub = function VueComponent(options) {
|
||||
this._init(options);
|
||||
};
|
||||
@@ -200,7 +193,6 @@ Vue.extend = function hippyExtend(extendOptions) {
|
||||
Sub.cid = cid;
|
||||
Sub.options = mergeOptions(Super.options, extendOptions);
|
||||
Sub.super = Super;
|
||||
|
||||
// For props and computed properties, we define the proxy getters on
|
||||
// the Vue instances at extension time, on the extended prototype. This
|
||||
// avoids Object.defineProperty calls for each instance created.
|
||||
@@ -210,12 +202,10 @@ Vue.extend = function hippyExtend(extendOptions) {
|
||||
if (Sub.options.computed) {
|
||||
initComputed(Sub);
|
||||
}
|
||||
|
||||
// allow further extension/mixin/plugin usage
|
||||
Sub.extend = Super.extend;
|
||||
Sub.mixin = Super.mixin;
|
||||
Sub.use = Super.use;
|
||||
|
||||
// create asset registers, so extended classes
|
||||
// can have their private assets too.
|
||||
ASSET_TYPES.forEach((type) => {
|
||||
@@ -225,14 +215,12 @@ Vue.extend = function hippyExtend(extendOptions) {
|
||||
if (name) {
|
||||
Sub.options.components[name] = Sub;
|
||||
}
|
||||
|
||||
// keep a reference to the super options at extension time.
|
||||
// later at instantiation we can check if Super's options have
|
||||
// been updated.
|
||||
Sub.superOptions = Super.options;
|
||||
Sub.extendOptions = extendOptions;
|
||||
Sub.sealedOptions = extend({}, Sub.options);
|
||||
|
||||
// cache constructor
|
||||
cachedCtors[SuperId] = Sub;
|
||||
return Sub;
|
||||
|
||||
Reference in New Issue
Block a user