1 | import { ATTR_KEY } from '../constants';
|
2 | import { isSameNodeType, isNamedNode } from './index';
|
3 | import { buildComponentFromVNode } from './component';
|
4 | import { createNode, setAccessor } from '../dom/index';
|
5 | import { unmountComponent } from './component';
|
6 | import options from '../options';
|
7 | import { removeNode } from '../dom/index';
|
8 |
|
9 |
|
10 | export const mounts = [];
|
11 |
|
12 |
|
13 | export let diffLevel = 0;
|
14 |
|
15 |
|
16 | let isSvgMode = false;
|
17 |
|
18 |
|
19 | let hydrating = false;
|
20 |
|
21 |
|
22 | export function flushMounts() {
|
23 | let c;
|
24 | while ((c=mounts.pop())) {
|
25 | if (options.afterMount) options.afterMount(c);
|
26 | if (c.componentDidMount) c.componentDidMount();
|
27 | }
|
28 | }
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 | export function diff(dom, vnode, context, mountAll, parent, componentRoot) {
|
38 |
|
39 | if (!diffLevel++) {
|
40 |
|
41 | isSvgMode = parent!=null && parent.ownerSVGElement!==undefined;
|
42 |
|
43 |
|
44 | hydrating = dom!=null && !(ATTR_KEY in dom);
|
45 | }
|
46 |
|
47 | let ret = idiff(dom, vnode, context, mountAll, componentRoot);
|
48 |
|
49 |
|
50 | if (parent && ret.parentNode!==parent) parent.appendChild(ret);
|
51 |
|
52 |
|
53 | if (!--diffLevel) {
|
54 | hydrating = false;
|
55 |
|
56 | if (!componentRoot) flushMounts();
|
57 | }
|
58 |
|
59 | return ret;
|
60 | }
|
61 |
|
62 |
|
63 |
|
64 | function idiff(dom, vnode, context, mountAll, componentRoot) {
|
65 | let out = dom,
|
66 | prevSvgMode = isSvgMode;
|
67 |
|
68 |
|
69 | if (vnode==null || typeof vnode==='boolean') vnode = '';
|
70 |
|
71 |
|
72 |
|
73 | if (typeof vnode==='string' || typeof vnode==='number') {
|
74 |
|
75 |
|
76 | if (dom && dom.splitText!==undefined && dom.parentNode && (!dom._component || componentRoot)) {
|
77 |
|
78 | if (dom.nodeValue!=vnode) {
|
79 | dom.nodeValue = vnode;
|
80 | }
|
81 | }
|
82 | else {
|
83 |
|
84 | out = document.createTextNode(vnode);
|
85 | if (dom) {
|
86 | if (dom.parentNode) dom.parentNode.replaceChild(out, dom);
|
87 | recollectNodeTree(dom, true);
|
88 | }
|
89 | }
|
90 |
|
91 | out[ATTR_KEY] = true;
|
92 |
|
93 | return out;
|
94 | }
|
95 |
|
96 |
|
97 |
|
98 | let vnodeName = vnode.nodeName;
|
99 | if (typeof vnodeName==='function') {
|
100 | return buildComponentFromVNode(dom, vnode, context, mountAll);
|
101 | }
|
102 |
|
103 |
|
104 |
|
105 | isSvgMode = vnodeName==='svg' ? true : vnodeName==='foreignObject' ? false : isSvgMode;
|
106 |
|
107 |
|
108 |
|
109 | vnodeName = String(vnodeName);
|
110 | if (!dom || !isNamedNode(dom, vnodeName)) {
|
111 | out = createNode(vnodeName, isSvgMode);
|
112 |
|
113 | if (dom) {
|
114 |
|
115 | while (dom.firstChild) out.appendChild(dom.firstChild);
|
116 |
|
117 |
|
118 | if (dom.parentNode) dom.parentNode.replaceChild(out, dom);
|
119 |
|
120 |
|
121 | recollectNodeTree(dom, true);
|
122 | }
|
123 | }
|
124 |
|
125 |
|
126 | let fc = out.firstChild,
|
127 | props = out[ATTR_KEY],
|
128 | vchildren = vnode.children;
|
129 |
|
130 | if (props==null) {
|
131 | props = out[ATTR_KEY] = {};
|
132 | for (let a=out.attributes, i=a.length; i--; ) props[a[i].name] = a[i].value;
|
133 | }
|
134 |
|
135 |
|
136 | if (!hydrating && vchildren && vchildren.length===1 && typeof vchildren[0]==='string' && fc!=null && fc.splitText!==undefined && fc.nextSibling==null) {
|
137 | if (fc.nodeValue!=vchildren[0]) {
|
138 | fc.nodeValue = vchildren[0];
|
139 | }
|
140 | }
|
141 |
|
142 | else if (vchildren && vchildren.length || fc!=null) {
|
143 | innerDiffNode(out, vchildren, context, mountAll, hydrating || props.dangerouslySetInnerHTML!=null);
|
144 | }
|
145 |
|
146 |
|
147 |
|
148 | diffAttributes(out, vnode.attributes, props);
|
149 |
|
150 |
|
151 |
|
152 | isSvgMode = prevSvgMode;
|
153 |
|
154 | return out;
|
155 | }
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 |
|
164 |
|
165 | function innerDiffNode(dom, vchildren, context, mountAll, isHydrating) {
|
166 | let originalChildren = dom.childNodes,
|
167 | children = [],
|
168 | keyed = {},
|
169 | keyedLen = 0,
|
170 | min = 0,
|
171 | len = originalChildren.length,
|
172 | childrenLen = 0,
|
173 | vlen = vchildren ? vchildren.length : 0,
|
174 | j, c, f, vchild, child;
|
175 |
|
176 |
|
177 | if (len!==0) {
|
178 | for (let i=0; i<len; i++) {
|
179 | let child = originalChildren[i],
|
180 | props = child[ATTR_KEY],
|
181 | key = vlen && props ? child._component ? child._component.__key : props.key : null;
|
182 | if (key!=null) {
|
183 | keyedLen++;
|
184 | keyed[key] = child;
|
185 | }
|
186 | else if (props || (child.splitText!==undefined ? (isHydrating ? child.nodeValue.trim() : true) : isHydrating)) {
|
187 | children[childrenLen++] = child;
|
188 | }
|
189 | }
|
190 | }
|
191 |
|
192 | if (vlen!==0) {
|
193 | for (let i=0; i<vlen; i++) {
|
194 | vchild = vchildren[i];
|
195 | child = null;
|
196 |
|
197 |
|
198 | let key = vchild.key;
|
199 | if (key!=null) {
|
200 | if (keyedLen && keyed[key]!==undefined) {
|
201 | child = keyed[key];
|
202 | keyed[key] = undefined;
|
203 | keyedLen--;
|
204 | }
|
205 | }
|
206 |
|
207 | else if (!child && min<childrenLen) {
|
208 | for (j=min; j<childrenLen; j++) {
|
209 | if (children[j]!==undefined && isSameNodeType(c = children[j], vchild, isHydrating)) {
|
210 | child = c;
|
211 | children[j] = undefined;
|
212 | if (j===childrenLen-1) childrenLen--;
|
213 | if (j===min) min++;
|
214 | break;
|
215 | }
|
216 | }
|
217 | }
|
218 |
|
219 |
|
220 | child = idiff(child, vchild, context, mountAll);
|
221 |
|
222 | f = originalChildren[i];
|
223 | if (child && child!==dom && child!==f) {
|
224 | if (f==null) {
|
225 | dom.appendChild(child);
|
226 | }
|
227 | else if (child===f.nextSibling) {
|
228 | removeNode(f);
|
229 | }
|
230 | else {
|
231 | dom.insertBefore(child, f);
|
232 | }
|
233 | }
|
234 | }
|
235 | }
|
236 |
|
237 |
|
238 |
|
239 | if (keyedLen) {
|
240 | for (let i in keyed) if (keyed[i]!==undefined) recollectNodeTree(keyed[i], false);
|
241 | }
|
242 |
|
243 |
|
244 | while (min<=childrenLen) {
|
245 | if ((child = children[childrenLen--])!==undefined) recollectNodeTree(child, false);
|
246 | }
|
247 | }
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 | export function recollectNodeTree(node, unmountOnly) {
|
256 | let component = node._component;
|
257 | if (component) {
|
258 |
|
259 | unmountComponent(component);
|
260 | }
|
261 | else {
|
262 |
|
263 |
|
264 | if (node[ATTR_KEY]!=null && node[ATTR_KEY].ref) node[ATTR_KEY].ref(null);
|
265 |
|
266 | if (unmountOnly===false || node[ATTR_KEY]==null) {
|
267 | removeNode(node);
|
268 | }
|
269 |
|
270 | removeChildren(node);
|
271 | }
|
272 | }
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | export function removeChildren(node) {
|
280 | node = node.lastChild;
|
281 | while (node) {
|
282 | let next = node.previousSibling;
|
283 | recollectNodeTree(node, true);
|
284 | node = next;
|
285 | }
|
286 | }
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 | function diffAttributes(dom, attrs, old) {
|
295 | let name;
|
296 |
|
297 |
|
298 | for (name in old) {
|
299 | if (!(attrs && attrs[name]!=null) && old[name]!=null) {
|
300 | setAccessor(dom, name, old[name], old[name] = undefined, isSvgMode);
|
301 | }
|
302 | }
|
303 |
|
304 |
|
305 | for (name in attrs) {
|
306 | if (name!=='children' && name!=='innerHTML' && (!(name in old) || attrs[name]!==(name==='value' || name==='checked' ? dom[name] : old[name]))) {
|
307 | setAccessor(dom, name, old[name], old[name] = attrs[name], isSvgMode);
|
308 | }
|
309 | }
|
310 | }
|