From 5704b72542052f8b0e00aa4b168b9851aacbbf9a Mon Sep 17 00:00:00 2001 From: Miel Truyen Date: Sat, 9 Nov 2019 00:50:32 +0100 Subject: [PATCH] Supporting the key-prop --- .../csx-custom-elements/src/vdom/render.js | 56 ++++++++++++------- .../src/vdom/renderers/nodetree.js | 2 +- .../src/vdom/types/render-state.js | 11 +++- test/todos-mvc/components/my-todo.jsx | 1 + 4 files changed, 49 insertions(+), 21 deletions(-) diff --git a/packages/csx-custom-elements/src/vdom/render.js b/packages/csx-custom-elements/src/vdom/render.js index 90ae977..ea8bfcd 100644 --- a/packages/csx-custom-elements/src/vdom/render.js +++ b/packages/csx-custom-elements/src/vdom/render.js @@ -26,17 +26,6 @@ export function getNodeMeta(vnode){ * @property {Element} [parent] - The parent element (TODO not sure what this will do when specified; Insert it as child element of the parent where?) */ -/** - * Temporary data structure for listing an old VNode - * @typedef VOldQueueItem - * @interface - * @category VDOM.renderer - * @property {VNode} vnode - The old vnode - * @property {VRenderQueueItemMetadata} meta - Meta data for the item such as normedType and the renderer to use(from a preprocessing stage) - * @property {Element} element - The matching element - **/ - - /** * This exists as a very basic example/test for JSX-to-DOM * @category VDOM @@ -103,7 +92,7 @@ export function render(vnode, opts = {}) { // Only items with a renderer are tracked (any other are undefined or null and shoulnd't be rendered at all) let childType = meta.normedType; if(!meta.renderer.remove) childType = 'node'; // Treat anything that doesnt have a special remove-function as ChildNode-type (e.g. it shows up in Element.childNodes) - childTypes.add(childType);// Tract that children of this type exist and should be iterated later + childTypes.add(childType);// Track that children of this type exist and should be iterated later vChildren[childType] = vChildren[childType] || []; // Make sure the array exists vChildren[childType].push({ item: { @@ -142,13 +131,17 @@ export function render(vnode, opts = {}) { curElement = curElement.nextSibling; } } - childTypes.add(childType);// Tract that children of this type exist and should be iterated later + childTypes.add(childType);// Track that children of this type exist and should be iterated later oldVChildren[childType] = oldVChildren[childType] || []; // Make sure the array exists - oldVChildren[childType].push({ + let oldItem = { vnode: next, element: childElement, meta: meta - }); + }; + oldVChildren[childType].push(oldItem); + if(next.props?.key){ + state.keyedElements.set(next.key,oldItem); + } } } } @@ -156,17 +149,42 @@ export function render(vnode, opts = {}) { let sortedChildTypes = Array.from(childTypes).sort((a,b)=>a==='node'?1:-1); // Always do ChildNode-types last let queuedItems = []; - let previous = null; + /**@type {VRenderQueueItem}*/ let previous = null; for(let childType of sortedChildTypes){ let newChildren = vChildren[childType]; let oldChildren = oldVChildren[childType]; while(newChildren && newChildren.length){ let child = newChildren.splice(0,1)[0]; - let oldChild = oldChildren && oldChildren.splice(0,1)[0]; - + + // Key handling + let childKey = child.item.vnode.props?.key; + /**@type {VOldQueueItem}*/ let oldChild; + if(childKey){ + oldChild = state.keyedElements.get(childKey); + if(oldChild) { + if (oldChildren && oldChildren[ 0 ] === oldChild) { + // Old keyed child already in the right place (just clear it from the queue); + oldChildren.splice(0, 1); + } else { + // Old keyed child not already in the right place + let indexOfKeyed = oldChildren.indexOf(oldChild); + if(indexOfKeyed) { + oldChildren.splice(indexOfKeyed, 1); + item.host.removeChild(oldChild.element); + } + if (previous) { + previous.item.host.after(oldChild.element); + } else { + item.parent.prepend(oldChild.element); + } + } + } + } + if(!oldChild) oldChild = oldChildren && oldChildren.splice(0,1)[0]; + child.previous = previous; - if(oldChild && child.meta.normedType === oldChild.meta.normedType){ + if(oldChild && child.meta.normedType === oldChild.meta.normedType && childKey===oldChild.vnode.props?.key){ // Update old-child child.item.host = oldChild.element; child.item.old = oldChild.vnode; diff --git a/packages/csx-custom-elements/src/vdom/renderers/nodetree.js b/packages/csx-custom-elements/src/vdom/renderers/nodetree.js index 7393db4..0ecfbf6 100644 --- a/packages/csx-custom-elements/src/vdom/renderers/nodetree.js +++ b/packages/csx-custom-elements/src/vdom/renderers/nodetree.js @@ -70,7 +70,7 @@ export const NodeTreeRenderer = { propDiffs.push([key,newVal, undefined]); } } - + // Now apply each for(let [key, newVal, oldVal] of propDiffs){ if(VNODEPROP_IGNORE[key]){ diff --git a/packages/csx-custom-elements/src/vdom/types/render-state.js b/packages/csx-custom-elements/src/vdom/types/render-state.js index 0ca42db..9d40f0d 100644 --- a/packages/csx-custom-elements/src/vdom/types/render-state.js +++ b/packages/csx-custom-elements/src/vdom/types/render-state.js @@ -24,6 +24,15 @@ import "./vnode"; * @property {VRenderItem} previous - The item that will have been inserted before this one **/ +/** + * Temporary data structure for listing an old VNode + * @typedef VOldQueueItem + * @interface + * @category VDOM.renderer + * @property {VNode} vnode - The old vnode + * @property {VRenderQueueItemMetadata} meta - Meta data for the item such as normedType and the renderer to use(from a preprocessing stage) + * @property {Element} element - The matching element + **/ /** * Global rendering-state when rendering a tree of VNodes @@ -32,5 +41,5 @@ import "./vnode"; * @category VDOM.renderer * @property {Array.} queue - The queue of items to be rendered * @property {Array.<[Function,Element]>} refs - Ref-callback functions be called when rendering is done - * @property {Map.} keyedElements - A map of keyed elements (TODO this needs refining) + * @property {Map.} keyedElements - A map of (old) keyed elements **/ diff --git a/test/todos-mvc/components/my-todo.jsx b/test/todos-mvc/components/my-todo.jsx index f9e6644..3512ac7 100644 --- a/test/todos-mvc/components/my-todo.jsx +++ b/test/todos-mvc/components/my-todo.jsx @@ -25,6 +25,7 @@ export class MyTodo extends CustomElement{ > {this.todos.map(item =>