/
children.js
170 lines (148 loc) · 6.53 KB
/
children.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import { diff, unmount, applyRef } from './index';
import { coerceToVNode } from '../create-element';
import { EMPTY_OBJ, EMPTY_ARR } from '../constants';
import { removeNode } from '../util';
/**
* Diff the children of a virtual node
* @param {import('../internal').PreactElement} parentDom The DOM element whose
* children are being diffed
* @param {import('../internal').VNode} newParentVNode The new virtual
* node whose children should be diff'ed against oldParentVNode
* @param {import('../internal').VNode} oldParentVNode The old virtual
* node whose children should be diff'ed against newParentVNode
* @param {object} context The current context object
* @param {boolean} isSvg Whether or not this DOM node is an SVG node
* @param {Array<import('../internal').PreactElement>} excessDomChildren
* @param {Array<import('../internal').Component>} mounts The list of components
* which have mounted
* @param {Node | Text} oldDom The current attached DOM
* element any new dom elements should be placed around. Likely `null` on first
* render (except when hydrating). Can be a sibling DOM element when diffing
* Fragments that have siblings. In most cases, it starts out as `oldChildren[0]._dom`.
*/
export function diffChildren(parentDom, newParentVNode, oldParentVNode, context, isSvg, excessDomChildren, mounts, oldDom) {
let childVNode, i, j, oldVNode, newDom, sibDom, firstChildDom, refs;
let newChildren = newParentVNode._children || (newParentVNode._children = Array.isArray(j = newParentVNode.props.children) ? j : [j]);
// This is a compression of oldParentVNode!=null && oldParentVNode != EMPTY_OBJ && oldParentVNode._children || EMPTY_ARR
// as EMPTY_OBJ._children should be `undefined`.
let oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;
let oldChildrenLength = oldChildren.length;
// Only in very specific places should this logic be invoked (top level `render` and `diffElementNodes`).
// I'm using `EMPTY_OBJ` to signal when `diffChildren` is invoked in these situations. I can't use `null`
// for this purpose, because `null` is a valid value for `oldDom` which can mean to skip to this logic
// (e.g. if mounting a new tree in which the old DOM should be ignored (usually for Fragments).
if (oldDom == EMPTY_OBJ) {
oldDom = null;
if (excessDomChildren!=null) {
for (i = 0; !oldDom && i < excessDomChildren.length; i++) {
oldDom = excessDomChildren[i];
}
}
else {
for (i = 0; !oldDom && i < oldChildrenLength; i++) {
oldDom = oldChildren[i] && oldChildren[i]._dom;
}
}
}
for (i=0; i<newChildren.length; i++) {
childVNode = newChildren[i] = coerceToVNode(newChildren[i]);
if (childVNode!=null) {
childVNode._parent = newParentVNode;
childVNode._depth = newParentVNode._depth + 1;
// Check if we find a corresponding element in oldChildren.
// If found, delete the array item by setting to `undefined`.
// We use `undefined`, as `null` is reserved for empty placeholders
// (holes).
oldVNode = oldChildren[i];
if (oldVNode===null || (oldVNode && childVNode.key == oldVNode.key && childVNode.type === oldVNode.type)) {
oldChildren[i] = undefined;
}
else {
// Either oldVNode === undefined or oldChildrenLength > 0,
// so after this loop oldVNode == null or oldVNode is a valid value.
for (j=0; j<oldChildrenLength; j++) {
oldVNode = oldChildren[j];
// If childVNode is unkeyed, we only match similarly unkeyed nodes, otherwise we match by key.
// We always match by type (in either case).
if (oldVNode && childVNode.key == oldVNode.key && childVNode.type === oldVNode.type) {
oldChildren[j] = undefined;
break;
}
oldVNode = null;
}
}
oldVNode = oldVNode || EMPTY_OBJ;
// Morph the old element into the new one, but don't append it to the dom yet
newDom = diff(parentDom, childVNode, oldVNode, context, isSvg, excessDomChildren, mounts, null, oldDom);
if ((j = childVNode.ref) && oldVNode.ref != j) {
(refs || (refs=[])).push(j, childVNode._component || newDom);
}
// Only proceed if the vnode has not been unmounted by `diff()` above.
if (newDom!=null) {
if (firstChildDom == null) {
firstChildDom = newDom;
}
if (childVNode._lastDomChild != null) {
// Only Fragments or components that return Fragment like VNodes will
// have a non-null _lastDomChild. Continue the diff from the end of
// this Fragment's DOM tree.
newDom = childVNode._lastDomChild;
}
else if (excessDomChildren==oldVNode || newDom!=oldDom || newDom.parentNode==null) {
// NOTE: excessDomChildren==oldVNode above:
// This is a compression of excessDomChildren==null && oldVNode==null!
// The values only have the same type when `null`.
outer: if (oldDom==null || oldDom.parentNode!==parentDom) {
parentDom.appendChild(newDom);
}
else {
// `j<oldChildrenLength; j+=2` is an alternative to `j++<oldChildrenLength/2`
for (sibDom=oldDom, j=0; (sibDom=sibDom.nextSibling) && j<oldChildrenLength; j+=2) {
if (sibDom==newDom) {
break outer;
}
}
parentDom.insertBefore(newDom, oldDom);
}
}
oldDom = newDom.nextSibling;
if (typeof newParentVNode.type == 'function') {
// At this point, if childVNode._lastDomChild existed, then
// newDom = childVNode._lastDomChild per line 101
newParentVNode._lastDomChild = newDom;
}
}
}
}
newParentVNode._dom = firstChildDom;
// Remove children that are not part of any vnode.
if (excessDomChildren!=null && typeof newParentVNode.type !== 'function') for (i=excessDomChildren.length; i--; ) if (excessDomChildren[i]!=null) removeNode(excessDomChildren[i]);
// Remove remaining oldChildren if there are any.
for (i=oldChildrenLength; i--; ) if (oldChildren[i]!=null) unmount(oldChildren[i], newParentVNode);
// Set refs only after unmount
if (refs) {
for (i = 0; i < refs.length; i++) {
applyRef(refs[i], refs[++i], newParentVNode);
}
}
}
/**
* Flatten a virtual nodes children to a single dimensional array
* @param {import('../index').ComponentChildren} children The unflattened
* children of a virtual node
* @param {Array<import('../internal').VNode | null>} [flattened] An flat array of children to modify
*/
export function toChildArray(children, flattened) {
if (flattened == null) flattened = [];
if (children==null || typeof children === 'boolean') {
}
else if (Array.isArray(children)) {
for (let i=0; i < children.length; i++) {
toChildArray(children[i], flattened);
}
}
else {
flattened.push(children);
}
return flattened;
}