daiwei 1 месяц назад
Родитель
Сommit
9128f84cc1
1 измененных файлов с 126 добавлено и 163 удалено
  1. 126 163
      packages/runtime-vapor/src/apiCreateFor.ts

+ 126 - 163
packages/runtime-vapor/src/apiCreateFor.ts

@@ -11,7 +11,7 @@ import {
   toReadonly,
   watch,
 } from '@vue/reactivity'
-import { isArray, isObject, isString } from '@vue/shared'
+import { getSequence, isArray, isObject, isString } from '@vue/shared'
 import { createComment, createTextNode } from './dom/node'
 import {
   type Block,
@@ -50,9 +50,6 @@ import {
 class ForBlock extends VaporFragment {
   scope: EffectScope | undefined
   key: any
-  prev?: ForBlock
-  next?: ForBlock
-  prevAnchor?: ForBlock
 
   itemRef: ShallowRef<any>
   keyRef: ShallowRef<any> | undefined
@@ -234,169 +231,151 @@ export const createFor = (
           }
         }
 
-        const commonLength = Math.min(oldLength, newLength)
-        const oldKeyIndexPairs: [any, number][] = new Array(oldLength)
-        const queuedBlocks: [
-          index: number,
-          item: ReturnType<typeof getItem>,
-          key: any,
-        ][] = new Array(newLength)
-
-        let endOffset = 0
-        let queuedBlocksLength = 0
-        let oldKeyIndexPairsLength = 0
-
-        while (endOffset < commonLength) {
-          const index = newLength - endOffset - 1
-          const item = getItem(source, index)
-          const key = getKey(...item)
-          const existingBlock = oldBlocks[oldLength - endOffset - 1]
-          if (existingBlock.key !== key) break
-          update(existingBlock, ...item)
-          newBlocks[index] = existingBlock
-          endOffset++
-        }
-
-        const e1 = commonLength - endOffset
-        const e2 = oldLength - endOffset
-        const e3 = newLength - endOffset
+        let i = 0
+        let e1 = oldLength - 1
+        let e2 = newLength - 1
 
-        for (let i = 0; i < e1; i++) {
-          const currentItem = getItem(source, i)
-          const currentKey = getKey(...currentItem)
+        while (i <= e1 && i <= e2) {
           const oldBlock = oldBlocks[i]
-          const oldKey = oldBlock.key
-          if (oldKey === currentKey) {
-            update((newBlocks[i] = oldBlock), currentItem[0])
-          } else {
-            queuedBlocks[queuedBlocksLength++] = [i, currentItem, currentKey]
-            oldKeyIndexPairs[oldKeyIndexPairsLength++] = [oldKey, i]
-          }
+          const item = getItem(source, i)
+          if (oldBlock.key !== getKey(...item)) break
+          update((newBlocks[i] = oldBlock), ...item)
+          i++
         }
 
-        for (let i = e1; i < e2; i++) {
-          oldKeyIndexPairs[oldKeyIndexPairsLength++] = [oldBlocks[i].key, i]
+        while (i <= e1 && i <= e2) {
+          const oldBlock = oldBlocks[e1]
+          const item = getItem(source, e2)
+          if (oldBlock.key !== getKey(...item)) break
+          update((newBlocks[e2] = oldBlock), ...item)
+          e1--
+          e2--
         }
 
-        for (let i = e1; i < e3; i++) {
-          const blockItem = getItem(source, i)
-          const blockKey = getKey(...blockItem)
-          queuedBlocks[queuedBlocksLength++] = [i, blockItem, blockKey]
-        }
+        if (i > e1) {
+          for (let j = e2; j >= i; j--) {
+            const nextIndex = j + 1
+            mount(
+              source,
+              j,
+              nextIndex < newLength
+                ? normalizeAnchor(newBlocks[nextIndex].nodes)
+                : parentAnchor,
+            )
+          }
+        } else if (i <= e2) {
+          const s1 = i
+          const s2 = i
+          const toBePatched = e2 - s2 + 1
+          const keyToNewIndexMap = new Map<any, number>()
+          const newItems = new Array<ReturnType<typeof getItem>>(toBePatched)
+          const newKeys = new Array<any>(toBePatched)
+          const nullKeyNewIndices: number[] = []
+
+          for (i = s2; i <= e2; i++) {
+            const item = getItem(source, i)
+            const key = getKey(...item)
+            const index = i - s2
+            newItems[index] = item
+            newKeys[index] = key
+            if (key != null) {
+              keyToNewIndexMap.set(key, i)
+            } else {
+              nullKeyNewIndices.push(i)
+            }
+          }
 
-        queuedBlocks.length = queuedBlocksLength
-        oldKeyIndexPairs.length = oldKeyIndexPairsLength
+          const newIndexToOldIndexMap = new Array(toBePatched).fill(0)
+          const unmountIndices: number[] = []
 
-        interface MountOper {
-          source: ResolvedSource
-          index: number
-          item: ReturnType<typeof getItem>
-          key: any
-        }
-        interface MoveOper {
-          index: number
-          block: ForBlock
-        }
+          let moved = false
+          let maxNewIndexSoFar = 0
+          let patched = 0
+          let nullKeyCursor = 0
 
-        const oldKeyIndexMap = new Map(oldKeyIndexPairs)
-        const opers: (MountOper | MoveOper)[] = new Array(queuedBlocks.length)
-
-        let mountCounter = 0
-        let opersLength = 0
-
-        for (let i = queuedBlocks.length - 1; i >= 0; i--) {
-          const [index, item, key] = queuedBlocks[i]
-          const oldIndex = oldKeyIndexMap.get(key)
-          if (oldIndex !== undefined) {
-            oldKeyIndexMap.delete(key)
-            const reusedBlock = (newBlocks[index] = oldBlocks[oldIndex])
-            update(reusedBlock, ...item)
-            opers[opersLength++] = { index, block: reusedBlock }
-          } else {
-            mountCounter++
-            opers[opersLength++] = { source, index, item, key }
+          for (i = s1; i <= e1; i++) {
+            const oldBlock = oldBlocks[i]
+            if (patched >= toBePatched) {
+              unmountIndices.push(i)
+              continue
+            }
+            let newIndex: number | undefined
+            if (oldBlock.key != null) {
+              newIndex = keyToNewIndexMap.get(oldBlock.key)
+            } else {
+              while (nullKeyCursor < nullKeyNewIndices.length) {
+                const j = nullKeyNewIndices[nullKeyCursor++]
+                const index = j - s2
+                if (newIndexToOldIndexMap[index] === 0) {
+                  newIndex = j
+                  break
+                }
+              }
+            }
+            if (newIndex === undefined) {
+              unmountIndices.push(i)
+            } else {
+              newIndexToOldIndexMap[newIndex - s2] = i + 1
+              if (newIndex >= maxNewIndexSoFar) {
+                maxNewIndexSoFar = newIndex
+              } else {
+                moved = true
+              }
+              const item = newItems[newIndex - s2]
+              update(oldBlock, ...item)
+              newBlocks[newIndex] = oldBlock
+              patched++
+            }
           }
-        }
-
-        const useFastRemove = mountCounter === newLength
 
-        for (const leftoverIndex of oldKeyIndexMap.values()) {
-          unmount(
-            oldBlocks[leftoverIndex],
-            !(useFastRemove && canUseFastRemove),
-            !useFastRemove,
-          )
-        }
-        if (useFastRemove) {
-          for (const selector of selectors) {
-            selector.cleanup()
-          }
-          if (canUseFastRemove) {
-            parent!.textContent = ''
-            parent!.appendChild(parentAnchor)
-          }
-        }
+          const mountCounter = toBePatched - patched
+          const useFastRemove = mountCounter === newLength
 
-        if (opers.length === mountCounter) {
-          for (const { source, index, item, key } of opers as MountOper[]) {
-            mount(
-              source,
-              index,
-              index < newLength - 1
-                ? normalizeAnchor(newBlocks[index + 1].nodes)
-                : parentAnchor,
-              item,
-              key,
+          for (const index of unmountIndices) {
+            unmount(
+              oldBlocks[index],
+              !(useFastRemove && canUseFastRemove),
+              !useFastRemove,
             )
           }
-        } else if (opers.length) {
-          let anchor = oldBlocks[0]
-          let blocksTail: ForBlock | undefined
-          for (let i = 0; i < oldLength; i++) {
-            const block = oldBlocks[i]
-            if (oldKeyIndexMap.has(block.key)) {
-              continue
+          if (useFastRemove) {
+            for (const selector of selectors) {
+              selector.cleanup()
             }
-            block.prevAnchor = anchor
-            anchor = oldBlocks[i + 1]
-            if (blocksTail !== undefined) {
-              blocksTail.next = block
-              block.prev = blocksTail
+            if (canUseFastRemove) {
+              parent!.textContent = ''
+              parent!.appendChild(parentAnchor)
             }
-            blocksTail = block
           }
-          for (const action of opers) {
-            const { index } = action
-            if (index < newLength - 1) {
-              const nextBlock = newBlocks[index + 1]
-              let anchorNode = normalizeAnchor(nextBlock.prevAnchor!.nodes)
-              if (!anchorNode.parentNode)
-                anchorNode = normalizeAnchor(nextBlock.nodes)
-              if ('source' in action) {
-                const { item, key } = action
-                const block = mount(source, index, anchorNode, item, key)
-                moveLink(block, nextBlock.prev, nextBlock)
-              } else if (action.block.next !== nextBlock) {
-                insert(action.block, parent!, anchorNode)
-                moveLink(action.block, nextBlock.prev, nextBlock)
+
+          const increasingNewIndexSequence = moved
+            ? getSequence(newIndexToOldIndexMap)
+            : []
+          let seqIdx = increasingNewIndexSequence.length - 1
+
+          if (mountCounter > 0 || moved) {
+            for (i = toBePatched - 1; i >= 0; i--) {
+              const newIndex = s2 + i
+              const nextIndex = newIndex + 1
+              const anchor =
+                nextIndex < newLength
+                  ? normalizeAnchor(newBlocks[nextIndex].nodes)
+                  : parentAnchor
+              if (newIndexToOldIndexMap[i] === 0) {
+                mount(source, newIndex, anchor, newItems[i], newKeys[i])
+              } else if (moved) {
+                if (seqIdx < 0 || i !== increasingNewIndexSequence[seqIdx]) {
+                  insert(newBlocks[newIndex], parent!, anchor)
+                } else {
+                  seqIdx--
+                }
               }
-            } else if ('source' in action) {
-              const { item, key } = action
-              const block = mount(source, index, parentAnchor, item, key)
-              moveLink(block, blocksTail)
-              blocksTail = block
-            } else if (action.block.next !== undefined) {
-              let anchorNode = anchor
-                ? normalizeAnchor(anchor.nodes)
-                : parentAnchor
-              if (!anchorNode.parentNode) anchorNode = parentAnchor
-              insert(action.block, parent!, anchorNode)
-              moveLink(action.block, blocksTail)
-              blocksTail = action.block
             }
           }
-          for (const block of newBlocks) {
-            block.prevAnchor = block.next = block.prev = undefined
+        } else {
+          while (i <= e1) {
+            unmount(oldBlocks[i])
+            i++
           }
         }
       }
@@ -573,22 +552,6 @@ export const createFor = (
   }
 }
 
-function moveLink(block: ForBlock, newPrev?: ForBlock, newNext?: ForBlock) {
-  const { prev: oldPrev, next: oldNext } = block
-  if (oldPrev) oldPrev.next = oldNext
-  if (oldNext) {
-    oldNext.prev = oldPrev
-    if (block.prevAnchor !== block) {
-      oldNext.prevAnchor = block.prevAnchor
-    }
-  }
-  if (newPrev) newPrev.next = block
-  if (newNext) newNext.prev = block
-  block.prev = newPrev
-  block.next = newNext
-  block.prevAnchor = block
-}
-
 export function createForSlots(
   rawSource: Source,
   getSlot: (item: any, key: any, index?: number) => DynamicSlot,