From 8b35ba6bdbe4bfaf2f3ac866ed05ebfed2df3d8e Mon Sep 17 00:00:00 2001 From: zoomchan-cxj Date: Wed, 17 Nov 2021 20:59:10 +0800 Subject: [PATCH] feat(react,vue): improve managing node performance --- docs/hippy-vue/native-event.md | 16 ++- packages/hippy-react/src/dom/view-node.ts | 134 ++++-------------- packages/hippy-react/src/hippy.ts | 2 +- .../hippy-react/src/renderer/host-configs.ts | 2 +- packages/hippy-react/src/renderer/render.ts | 38 +++-- .../hippy-vue/src/renderer/element-node.js | 21 +-- .../hippy-vue/src/renderer/native/index.js | 62 +++----- packages/hippy-vue/src/renderer/view-node.js | 60 ++------ packages/hippy-vue/src/runtime/index.js | 18 +-- 9 files changed, 100 insertions(+), 253 deletions(-) diff --git a/docs/hippy-vue/native-event.md b/docs/hippy-vue/native-event.md index 55a010fe1..3b5e59a54 100644 --- a/docs/hippy-vue/native-event.md +++ b/docs/hippy-vue/native-event.md @@ -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); ``` diff --git a/packages/hippy-react/src/dom/view-node.ts b/packages/hippy-react/src/dom/view-node.ts index c6293d4ae..a12c049fe 100644 --- a/packages/hippy-react/src/dom/view-node.ts +++ b/packages/hippy-react/src/dom/view-node.ts @@ -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); }); } } diff --git a/packages/hippy-react/src/hippy.ts b/packages/hippy-react/src/hippy.ts index fd5a12c8f..784229592 100644 --- a/packages/hippy-react/src/hippy.ts +++ b/packages/hippy-react/src/hippy.ts @@ -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); diff --git a/packages/hippy-react/src/renderer/host-configs.ts b/packages/hippy-react/src/renderer/host-configs.ts index b7ed8e072..5608cc6af 100644 --- a/packages/hippy-react/src/renderer/host-configs.ts +++ b/packages/hippy-react/src/renderer/host-configs.ts @@ -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; diff --git a/packages/hippy-react/src/renderer/render.ts b/packages/hippy-react/src/renderer/render.ts index cef315bf7..dca1f93d9 100644 --- a/packages/hippy-react/src/renderer/render.ts +++ b/packages/hippy-react/src/renderer/render.ts @@ -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, diff --git a/packages/hippy-vue/src/renderer/element-node.js b/packages/hippy-vue/src/renderer/element-node.js index 30f4003e3..d597e3cff 100644 --- a/packages/hippy-vue/src/renderer/element-node.js +++ b/packages/hippy-vue/src/renderer/element-node.js @@ -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(''); } diff --git a/packages/hippy-vue/src/renderer/native/index.js b/packages/hippy-vue/src/renderer/native/index.js index 33428d910..c2325dfb9 100644 --- a/packages/hippy-vue/src/renderer/native/index.js +++ b/packages/hippy-vue/src/renderer/native/index.js @@ -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, diff --git a/packages/hippy-vue/src/renderer/view-node.js b/packages/hippy-vue/src/renderer/view-node.js index 285d9a4b6..ac1f40a25 100644 --- a/packages/hippy-vue/src/renderer/view-node.js +++ b/packages/hippy-vue/src/renderer/view-node.js @@ -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) => { diff --git a/packages/hippy-vue/src/runtime/index.js b/packages/hippy-vue/src/runtime/index.js index 6513ca050..b5e47afb9 100644 --- a/packages/hippy-vue/src/runtime/index.js +++ b/packages/hippy-vue/src/runtime/index.js @@ -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;