@wsxjs/wsx-core 0.0.22 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-BPQGLNOQ.mjs +1140 -0
- package/dist/chunk-ESZYREJK.mjs +1132 -0
- package/dist/chunk-OGMB43J4.mjs +1131 -0
- package/dist/chunk-TKHKPLBM.mjs +1142 -0
- package/dist/index.js +224 -183
- package/dist/index.mjs +1 -1
- package/dist/jsx-runtime.js +224 -183
- package/dist/jsx-runtime.mjs +1 -1
- package/dist/jsx.js +224 -183
- package/dist/jsx.mjs +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +2 -2
- package/src/utils/element-update.ts +110 -304
- package/src/utils/update-children-helpers.ts +342 -0
|
@@ -6,10 +6,25 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { shouldUseSVGNamespace, getSVGAttributeName } from "./svg-utils";
|
|
9
|
-
import {
|
|
9
|
+
import { type JSXChildren } from "./dom-utils";
|
|
10
10
|
import { setSmartProperty, isFrameworkInternalProp } from "./props-utils";
|
|
11
|
-
import { shouldPreserveElement
|
|
11
|
+
import { shouldPreserveElement } from "./element-marking";
|
|
12
12
|
import type { DOMCacheManager } from "../dom-cache-manager";
|
|
13
|
+
import {
|
|
14
|
+
collectPreservedElements,
|
|
15
|
+
findElementNode,
|
|
16
|
+
findTextNode,
|
|
17
|
+
updateOrCreateTextNode,
|
|
18
|
+
removeNodeIfNotPreserved,
|
|
19
|
+
replaceOrInsertElement,
|
|
20
|
+
appendNewChild,
|
|
21
|
+
buildNewChildrenMaps,
|
|
22
|
+
deduplicateCacheKeys,
|
|
23
|
+
collectNodesToRemove,
|
|
24
|
+
removeNodes,
|
|
25
|
+
reinsertPreservedElements,
|
|
26
|
+
flattenChildrenSafe,
|
|
27
|
+
} from "./update-children-helpers";
|
|
13
28
|
|
|
14
29
|
/**
|
|
15
30
|
* Removes a property from an element.
|
|
@@ -236,205 +251,129 @@ export function updateProps(
|
|
|
236
251
|
export function updateChildren(
|
|
237
252
|
element: HTMLElement | SVGElement,
|
|
238
253
|
oldChildren: JSXChildren[],
|
|
239
|
-
newChildren: JSXChildren[]
|
|
254
|
+
newChildren: JSXChildren[],
|
|
255
|
+
cacheManager?: DOMCacheManager
|
|
240
256
|
): void {
|
|
241
|
-
const flatOld =
|
|
242
|
-
const flatNew =
|
|
257
|
+
const flatOld = flattenChildrenSafe(oldChildren);
|
|
258
|
+
const flatNew = flattenChildrenSafe(newChildren);
|
|
243
259
|
|
|
244
|
-
//
|
|
245
|
-
const
|
|
260
|
+
// 收集需要保留的元素(第三方库注入的元素)
|
|
261
|
+
const preservedElements = collectPreservedElements(element);
|
|
246
262
|
|
|
247
263
|
// 更新现有子节点
|
|
248
|
-
|
|
249
|
-
//
|
|
250
|
-
|
|
264
|
+
const minLength = Math.min(flatOld.length, flatNew.length);
|
|
265
|
+
const domIndex = { value: 0 }; // 使用对象包装,使其可在函数间传递
|
|
266
|
+
|
|
251
267
|
for (let i = 0; i < minLength; i++) {
|
|
252
268
|
const oldChild = flatOld[i];
|
|
253
269
|
const newChild = flatNew[i];
|
|
254
270
|
|
|
255
|
-
//
|
|
256
|
-
// 关键:oldChild 是上次渲染的元素引用,如果它在 DOM 中,直接使用它
|
|
271
|
+
// 查找对应的 DOM 节点
|
|
257
272
|
let oldNode: Node | null = null;
|
|
258
273
|
if (oldChild instanceof HTMLElement || oldChild instanceof SVGElement) {
|
|
259
|
-
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// oldChild 不在 DOM 中,尝试通过 cache key 找到对应的 DOM 节点
|
|
269
|
-
// 这可以处理元素被替换但 cache key 相同的情况
|
|
270
|
-
const oldCacheKey = getElementCacheKey(oldChild);
|
|
271
|
-
if (oldCacheKey) {
|
|
272
|
-
// 遍历 DOM 中的子节点,找到具有相同 cache key 的节点
|
|
273
|
-
for (let j = 0; j < element.childNodes.length; j++) {
|
|
274
|
-
const domChild = element.childNodes[j];
|
|
275
|
-
if (domChild instanceof HTMLElement || domChild instanceof SVGElement) {
|
|
276
|
-
// 跳过应该保留的元素(手动创建的元素、第三方库注入的元素)
|
|
277
|
-
if (shouldPreserveElement(domChild)) {
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
const domCacheKey = getElementCacheKey(domChild);
|
|
281
|
-
if (domCacheKey === oldCacheKey) {
|
|
282
|
-
oldNode = domChild;
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
274
|
+
oldNode = findElementNode(oldChild, element);
|
|
275
|
+
// 关键修复:当处理元素节点时,需要更新 domIndex 以跳过该元素
|
|
276
|
+
// 这样,下一个文本节点的查找位置才是正确的
|
|
277
|
+
if (oldNode && oldNode.parentNode === element) {
|
|
278
|
+
// 找到 oldNode 在 DOM 中的位置
|
|
279
|
+
const nodeIndex = Array.from(element.childNodes).indexOf(oldNode as ChildNode);
|
|
280
|
+
if (nodeIndex !== -1 && nodeIndex >= domIndex.value) {
|
|
281
|
+
// 更新 domIndex 到 oldNode 之后的位置
|
|
282
|
+
domIndex.value = nodeIndex + 1;
|
|
287
283
|
}
|
|
288
284
|
}
|
|
289
|
-
// 如果 oldChild 不在 DOM 中且找不到对应的节点,oldNode 保持为 null
|
|
290
285
|
} else if (typeof oldChild === "string" || typeof oldChild === "number") {
|
|
291
|
-
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
} else {
|
|
305
|
-
// 跳过其他类型的节点
|
|
306
|
-
domIndex++;
|
|
286
|
+
oldNode = findTextNode(element, domIndex);
|
|
287
|
+
// 关键修复:如果 findTextNode 返回 null,尝试从当前 domIndex 位置开始查找文本节点
|
|
288
|
+
// 这可以处理文本节点存在但 domIndex 不正确的情况
|
|
289
|
+
// Bug 1 修复:从 domIndex.value 开始搜索,而不是从 0 开始,避免重新处理已处理的节点
|
|
290
|
+
if (!oldNode && element.childNodes.length > 0) {
|
|
291
|
+
for (let j = domIndex.value; j < element.childNodes.length; j++) {
|
|
292
|
+
const node = element.childNodes[j];
|
|
293
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
294
|
+
oldNode = node;
|
|
295
|
+
// 更新 domIndex 到找到的文本节点之后
|
|
296
|
+
domIndex.value = j + 1;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
307
299
|
}
|
|
308
300
|
}
|
|
309
301
|
}
|
|
310
302
|
|
|
311
|
-
//
|
|
303
|
+
// 处理文本节点(oldChild 是字符串/数字)
|
|
312
304
|
if (typeof oldChild === "string" || typeof oldChild === "number") {
|
|
313
305
|
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
314
306
|
const oldText = String(oldChild);
|
|
315
307
|
const newText = String(newChild);
|
|
316
308
|
|
|
317
|
-
//
|
|
318
|
-
//
|
|
309
|
+
// Bug 2 修复:只有当文本内容确实需要更新时才调用 updateOrCreateTextNode
|
|
310
|
+
// 如果 oldText === newText 且 oldNode 为 null,说明文本节点可能已经存在且内容正确
|
|
311
|
+
// 或者不需要创建,因此不应该调用 updateOrCreateTextNode
|
|
319
312
|
const needsUpdate =
|
|
320
313
|
oldText !== newText ||
|
|
321
314
|
(oldNode &&
|
|
322
315
|
oldNode.nodeType === Node.TEXT_NODE &&
|
|
323
316
|
oldNode.textContent !== newText);
|
|
324
317
|
|
|
325
|
-
if (
|
|
326
|
-
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if (oldNode && oldNode.nodeType === Node.TEXT_NODE) {
|
|
331
|
-
// 更新现有文本节点
|
|
332
|
-
oldNode.textContent = newText;
|
|
333
|
-
} else {
|
|
334
|
-
// 创建新的文本节点
|
|
335
|
-
const newTextNode = document.createTextNode(newText);
|
|
336
|
-
if (oldNode && !shouldPreserveElement(oldNode)) {
|
|
337
|
-
element.replaceChild(newTextNode, oldNode);
|
|
338
|
-
} else {
|
|
339
|
-
element.insertBefore(newTextNode, oldNode || null);
|
|
340
|
-
}
|
|
318
|
+
if (needsUpdate) {
|
|
319
|
+
updateOrCreateTextNode(element, oldNode, newText);
|
|
341
320
|
}
|
|
321
|
+
// 如果文本内容相同且 oldNode 为 null,不需要做任何操作
|
|
322
|
+
// 因为文本节点可能已经存在于 DOM 中且内容正确,或者不需要创建
|
|
342
323
|
} else {
|
|
343
|
-
// 类型变化:文本 ->
|
|
344
|
-
|
|
345
|
-
element.removeChild(oldNode);
|
|
346
|
-
}
|
|
324
|
+
// 类型变化:文本 -> 元素/Fragment
|
|
325
|
+
removeNodeIfNotPreserved(element, oldNode);
|
|
347
326
|
if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
348
|
-
|
|
349
|
-
element.insertBefore(newChild, oldNode || null);
|
|
350
|
-
}
|
|
327
|
+
replaceOrInsertElement(element, newChild, oldNode);
|
|
351
328
|
} else if (newChild instanceof DocumentFragment) {
|
|
352
329
|
element.insertBefore(newChild, oldNode || null);
|
|
353
330
|
}
|
|
354
331
|
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
|
|
332
|
+
}
|
|
333
|
+
// 处理元素节点(oldChild 是元素)
|
|
334
|
+
else if (oldChild instanceof HTMLElement || oldChild instanceof SVGElement) {
|
|
358
335
|
if (oldNode && shouldPreserveElement(oldNode)) {
|
|
359
|
-
//
|
|
360
|
-
continue;
|
|
336
|
+
continue; // 跳过保留的元素
|
|
361
337
|
}
|
|
362
338
|
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
if (oldNode !== newChild) {
|
|
382
|
-
// 如果 newChild 已经在 DOM 中,需要先移除它(避免重复)
|
|
383
|
-
if (newChild.parentNode === element) {
|
|
384
|
-
// newChild 已经在当前 element 中
|
|
385
|
-
// 如果 cache key 相同,说明是同一个逻辑位置,应该替换 oldNode
|
|
386
|
-
if (hasSameCacheKey) {
|
|
387
|
-
// 如果 newChild 就是 oldNode,不需要替换
|
|
388
|
-
if (newChild !== oldNode) {
|
|
389
|
-
// 替换旧元素(replaceChild 会自动从 newChild 的旧位置移除它)
|
|
390
|
-
// 但是,如果 newChild 在 oldNode 之后,replaceChild 会先移除 newChild,然后替换 oldNode
|
|
391
|
-
// 这会导致 newChild 移动到 oldNode 的位置,这是正确的
|
|
392
|
-
// 如果 newChild 在 oldNode 之前,replaceChild 也会将 newChild 移动到 oldNode 的位置
|
|
393
|
-
// 所以,无论 newChild 在哪里,replaceChild 都会将其移动到 oldNode 的位置
|
|
394
|
-
element.replaceChild(newChild, oldNode);
|
|
395
|
-
}
|
|
396
|
-
} else {
|
|
397
|
-
// cache key 不同,说明 newChild 在错误的位置
|
|
398
|
-
// 先移除 newChild(它可能在错误的位置)
|
|
399
|
-
element.removeChild(newChild);
|
|
400
|
-
// 然后替换 oldNode
|
|
401
|
-
element.replaceChild(newChild, oldNode);
|
|
402
|
-
}
|
|
403
|
-
} else if (newChild.parentNode) {
|
|
404
|
-
// newChild 在其他父元素中,先移除
|
|
405
|
-
newChild.parentNode.removeChild(newChild);
|
|
406
|
-
// 然后替换 oldNode
|
|
407
|
-
element.replaceChild(newChild, oldNode);
|
|
408
|
-
} else {
|
|
409
|
-
// newChild 不在 DOM 中,直接替换
|
|
410
|
-
element.replaceChild(newChild, oldNode);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
} else {
|
|
414
|
-
// 应该保留旧节点,只添加新节点(不替换)
|
|
415
|
-
if (newChild.parentNode !== element) {
|
|
416
|
-
// 如果 newChild 在其他父元素中,先移除
|
|
417
|
-
if (newChild.parentNode) {
|
|
418
|
-
newChild.parentNode.removeChild(newChild);
|
|
419
|
-
}
|
|
420
|
-
element.insertBefore(newChild, oldNode.nextSibling);
|
|
339
|
+
// 关键修复:即使 newChild === oldChild,如果它是元素,h() 应该已经调用了 updateElement 来更新其子元素
|
|
340
|
+
// 但是,为了确保子元素确实被更新了,我们不应该跳过,而是让后续代码确保元素在正确位置
|
|
341
|
+
// 如果 h() 已经正确更新了子元素,那么这里只需要确保元素在正确位置即可
|
|
342
|
+
if (
|
|
343
|
+
newChild === oldChild &&
|
|
344
|
+
(newChild instanceof HTMLElement || newChild instanceof SVGElement)
|
|
345
|
+
) {
|
|
346
|
+
// 同一个元素引用,h() 应该已经通过 updateElement 更新了其子元素
|
|
347
|
+
// 但是,如果 cacheManager 可用,我们可以验证并确保子元素确实被更新了
|
|
348
|
+
if (cacheManager) {
|
|
349
|
+
const childMetadata = cacheManager.getMetadata(newChild);
|
|
350
|
+
if (childMetadata) {
|
|
351
|
+
// 如果元数据存在,说明 h() 已经更新了子元素
|
|
352
|
+
// 只需要确保元素在正确位置
|
|
353
|
+
if (oldNode === newChild && newChild.parentNode === element) {
|
|
354
|
+
// 元素已经在正确位置,且 h() 应该已经更新了其子元素
|
|
355
|
+
// 不需要额外处理
|
|
356
|
+
continue;
|
|
421
357
|
}
|
|
422
358
|
}
|
|
423
359
|
} else {
|
|
424
|
-
//
|
|
425
|
-
if (newChild.parentNode
|
|
426
|
-
|
|
427
|
-
if (newChild.parentNode) {
|
|
428
|
-
newChild.parentNode.removeChild(newChild);
|
|
429
|
-
}
|
|
430
|
-
element.appendChild(newChild);
|
|
360
|
+
// 如果没有 cacheManager,假设 h() 已经更新了子元素
|
|
361
|
+
if (oldNode === newChild && newChild.parentNode === element) {
|
|
362
|
+
continue;
|
|
431
363
|
}
|
|
432
364
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
365
|
+
// 如果位置不对,需要调整
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
369
|
+
// 如果 newChild 已经在 DOM 中且位置正确,不需要替换
|
|
370
|
+
if (newChild.parentNode === element && oldNode === newChild) {
|
|
371
|
+
continue; // 元素已经在正确位置,不需要更新
|
|
437
372
|
}
|
|
373
|
+
replaceOrInsertElement(element, newChild, oldNode);
|
|
374
|
+
} else {
|
|
375
|
+
// 类型变化:元素 -> 文本/Fragment
|
|
376
|
+
removeNodeIfNotPreserved(element, oldNode);
|
|
438
377
|
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
439
378
|
const newTextNode = document.createTextNode(String(newChild));
|
|
440
379
|
element.insertBefore(newTextNode, oldNode?.nextSibling || null);
|
|
@@ -447,158 +386,25 @@ export function updateChildren(
|
|
|
447
386
|
|
|
448
387
|
// 添加新子节点
|
|
449
388
|
for (let i = minLength; i < flatNew.length; i++) {
|
|
450
|
-
|
|
451
|
-
if (newChild === null || newChild === undefined || newChild === false) {
|
|
452
|
-
continue;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
if (typeof newChild === "string" || typeof newChild === "number") {
|
|
456
|
-
element.appendChild(document.createTextNode(String(newChild)));
|
|
457
|
-
} else if (newChild instanceof HTMLElement || newChild instanceof SVGElement) {
|
|
458
|
-
// 确保子元素正确添加到当前父容器
|
|
459
|
-
// 如果 newChild 已经在 DOM 中,需要检查它是否在正确的位置
|
|
460
|
-
if (newChild.parentNode === element) {
|
|
461
|
-
// newChild 已经在当前 element 中
|
|
462
|
-
// 检查它是否在正确的位置(应该在最后,因为这是"添加新子节点"部分)
|
|
463
|
-
const currentIndex = Array.from(element.childNodes).indexOf(newChild);
|
|
464
|
-
const expectedIndex = element.childNodes.length - 1;
|
|
465
|
-
if (currentIndex !== expectedIndex) {
|
|
466
|
-
// 位置不对,移动到正确位置(末尾)
|
|
467
|
-
element.removeChild(newChild);
|
|
468
|
-
element.appendChild(newChild);
|
|
469
|
-
}
|
|
470
|
-
// 如果位置正确,跳过(避免重复添加)
|
|
471
|
-
// 注意:元素内容会在 updateElement 中更新,所以这里只需要确保位置正确
|
|
472
|
-
continue;
|
|
473
|
-
} else if (newChild.parentNode) {
|
|
474
|
-
// newChild 在其他父元素中,先移除
|
|
475
|
-
newChild.parentNode.removeChild(newChild);
|
|
476
|
-
}
|
|
477
|
-
// 添加 newChild 到当前 element 的末尾
|
|
478
|
-
element.appendChild(newChild);
|
|
479
|
-
} else if (newChild instanceof DocumentFragment) {
|
|
480
|
-
element.appendChild(newChild);
|
|
481
|
-
}
|
|
389
|
+
appendNewChild(element, flatNew[i]);
|
|
482
390
|
}
|
|
483
391
|
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
|
|
487
|
-
const nodesToRemove: Node[] = [];
|
|
488
|
-
const newChildSet = new Set<HTMLElement | SVGElement | DocumentFragment>();
|
|
489
|
-
const newChildCacheKeyMap = new Map<string, HTMLElement | SVGElement>();
|
|
490
|
-
// 只将元素节点添加到 Set 中(文本节点不能直接比较)
|
|
491
|
-
for (const child of flatNew) {
|
|
492
|
-
if (
|
|
493
|
-
child instanceof HTMLElement ||
|
|
494
|
-
child instanceof SVGElement ||
|
|
495
|
-
child instanceof DocumentFragment
|
|
496
|
-
) {
|
|
497
|
-
newChildSet.add(child);
|
|
498
|
-
// 同时收集 cache key 到 Map,用于匹配(即使元素引用不同,cache key 相同也认为是同一个元素)
|
|
499
|
-
// 注意:DocumentFragment 没有 cache key,只能通过引用匹配
|
|
500
|
-
if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
501
|
-
const cacheKey = getElementCacheKey(child);
|
|
502
|
-
if (cacheKey) {
|
|
503
|
-
// 如果 cache key 已存在,保留最新的元素(newChild)
|
|
504
|
-
newChildCacheKeyMap.set(cacheKey, child);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
392
|
+
// 移除多余子节点(使用纯函数简化逻辑)
|
|
393
|
+
// 步骤 1: 构建新子元素的引用集合和 cache key 映射
|
|
394
|
+
const { elementSet, cacheKeyMap } = buildNewChildrenMaps(flatNew);
|
|
509
395
|
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
// 关键:这个步骤在"更新现有子节点"循环之后执行,所以需要处理那些在"更新现有子节点"循环中没有处理的重复元素
|
|
513
|
-
const processedCacheKeys = new Set<string>();
|
|
514
|
-
// 构建 newChild 到其在 flatNew 中索引的映射,用于确定正确位置
|
|
515
|
-
const newChildToIndexMap = new Map<HTMLElement | SVGElement, number>();
|
|
516
|
-
for (let i = 0; i < flatNew.length; i++) {
|
|
517
|
-
const child = flatNew[i];
|
|
518
|
-
if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
519
|
-
newChildToIndexMap.set(child, i);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
396
|
+
// 步骤 2: 处理重复的 cache key(确保每个 cache key 在 DOM 中只出现一次)
|
|
397
|
+
deduplicateCacheKeys(element, cacheKeyMap);
|
|
522
398
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
if (child instanceof HTMLElement || child instanceof SVGElement) {
|
|
526
|
-
// 关键修复:跳过应该保留的元素(第三方库注入的元素)
|
|
527
|
-
if (shouldPreserveElement(child)) {
|
|
528
|
-
continue;
|
|
529
|
-
}
|
|
530
|
-
const cacheKey = getElementCacheKey(child);
|
|
531
|
-
if (
|
|
532
|
-
cacheKey &&
|
|
533
|
-
newChildCacheKeyMap.has(cacheKey) &&
|
|
534
|
-
!processedCacheKeys.has(cacheKey)
|
|
535
|
-
) {
|
|
536
|
-
processedCacheKeys.add(cacheKey);
|
|
537
|
-
const newChild = newChildCacheKeyMap.get(cacheKey)!;
|
|
538
|
-
// 如果 child 不是 newChild,说明是旧元素,应该被移除或替换
|
|
539
|
-
if (child !== newChild) {
|
|
540
|
-
// 如果 newChild 已经在 DOM 中,移除旧元素
|
|
541
|
-
if (newChild.parentNode === element) {
|
|
542
|
-
// newChild 已经在 DOM 中,移除旧元素
|
|
543
|
-
// 注意:newChild 已经在 DOM 中,所以不需要再次添加
|
|
544
|
-
// 但需要确保 newChild 在正确的位置(通过 replaceChild 移动到旧元素的位置)
|
|
545
|
-
// 这样可以确保 newChild 在正确的位置,而不是在错误的位置
|
|
546
|
-
// 但是,如果 newChild 在 child 之后,replaceChild 会先移除 newChild,然后替换 child
|
|
547
|
-
// 这会导致 newChild 移动到 child 的位置,这是正确的
|
|
548
|
-
// 如果 newChild 在 child 之前,replaceChild 也会将 newChild 移动到 child 的位置
|
|
549
|
-
// 所以,无论 newChild 在哪里,replaceChild 都会将其移动到 child 的位置
|
|
550
|
-
element.replaceChild(newChild, child);
|
|
551
|
-
} else {
|
|
552
|
-
// newChild 不在 DOM 中,替换旧元素
|
|
553
|
-
element.replaceChild(newChild, child);
|
|
554
|
-
}
|
|
555
|
-
} else {
|
|
556
|
-
// child === newChild,说明是同一个元素,不需要处理
|
|
557
|
-
// 但需要确保它在正确的位置(这应该在"更新现有子节点"循环中处理)
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// 第二步:移除不在 newChildren 中的元素
|
|
564
|
-
for (let i = 0; i < element.childNodes.length; i++) {
|
|
565
|
-
const child = element.childNodes[i];
|
|
399
|
+
// 步骤 3: 收集需要移除的节点(跳过保留元素和新子元素)
|
|
400
|
+
const nodesToRemove = collectNodesToRemove(element, elementSet, cacheKeyMap);
|
|
566
401
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
402
|
+
// 步骤 4: 批量移除节点(从后往前,避免索引变化)
|
|
403
|
+
removeNodes(element, nodesToRemove);
|
|
571
404
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
if (newChildSet.has(child)) {
|
|
576
|
-
continue;
|
|
577
|
-
}
|
|
578
|
-
// 方法 2: cache key 匹配(用于处理语言切换等场景,元素引用可能不同但 cache key 相同)
|
|
579
|
-
const cacheKey = getElementCacheKey(child);
|
|
580
|
-
if (cacheKey && newChildCacheKeyMap.has(cacheKey)) {
|
|
581
|
-
// 已经在第一步处理过了,跳过
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
} else if (child instanceof DocumentFragment) {
|
|
585
|
-
// DocumentFragment 只能通过引用匹配
|
|
586
|
-
if (newChildSet.has(child)) {
|
|
587
|
-
continue;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
// 只有不应该保留且不在 newChildren 中的节点才添加到移除列表
|
|
592
|
-
nodesToRemove.push(child);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// 统一移除(从后往前移除,避免索引变化)
|
|
596
|
-
for (let i = nodesToRemove.length - 1; i >= 0; i--) {
|
|
597
|
-
const node = nodesToRemove[i];
|
|
598
|
-
if (node.parentNode === element) {
|
|
599
|
-
element.removeChild(node);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
405
|
+
// 步骤 5: 重新插入所有保留的元素到 DOM 末尾
|
|
406
|
+
// 这确保了第三方库注入的元素不会丢失
|
|
407
|
+
reinsertPreservedElements(element, preservedElements);
|
|
602
408
|
}
|
|
603
409
|
|
|
604
410
|
/**
|
|
@@ -629,5 +435,5 @@ export function updateElement(
|
|
|
629
435
|
updateProps(element, oldProps, newProps, tag);
|
|
630
436
|
|
|
631
437
|
// 更新 children
|
|
632
|
-
updateChildren(element, oldChildren, newChildren);
|
|
438
|
+
updateChildren(element, oldChildren, newChildren, cacheManager);
|
|
633
439
|
}
|