wip(vapor): optimize v-for getItem

This commit is contained in:
Evan You 2025-01-30 22:36:41 +08:00
parent c89e01efbf
commit 34a0ad7499
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
1 changed files with 49 additions and 31 deletions

View File

@ -1,4 +1,12 @@
import { EffectScope, type ShallowRef, shallowRef } from '@vue/reactivity' import {
EffectScope,
type ShallowRef,
isReactive,
isShallow,
shallowReadArray,
shallowRef,
toReactive,
} from '@vue/reactivity'
import { getSequence, isArray, isObject, isString } from '@vue/shared' import { getSequence, isArray, isObject, isString } from '@vue/shared'
import { createComment, createTextNode } from './dom/node' import { createComment, createTextNode } from './dom/node'
import { type Block, Fragment, insert, remove as removeBlock } from './block' import { type Block, Fragment, insert, remove as removeBlock } from './block'
@ -33,6 +41,12 @@ class ForBlock extends Fragment {
type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any> type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
type ResolvedSource = {
values: any[]
needsWrap: boolean
keys?: string[]
}
/*! #__NO_SIDE_EFFECTS__ */ /*! #__NO_SIDE_EFFECTS__ */
export const createFor = ( export const createFor = (
src: () => Source, src: () => Source,
@ -59,8 +73,8 @@ export const createFor = (
} }
const renderList = () => { const renderList = () => {
const source = src() const source = normalizeSource(src())
const newLength = getLength(source) const newLength = source.values.length
const oldLength = oldBlocks.length const oldLength = oldBlocks.length
newBlocks = new Array(newLength) newBlocks = new Array(newLength)
@ -85,8 +99,7 @@ export const createFor = (
// unkeyed fast path // unkeyed fast path
const commonLength = Math.min(newLength, oldLength) const commonLength = Math.min(newLength, oldLength)
for (let i = 0; i < commonLength; i++) { for (let i = 0; i < commonLength; i++) {
const [item] = getItem(source, i) update((newBlocks[i] = oldBlocks[i]), getItem(source, i)[0])
update((newBlocks[i] = oldBlocks[i]), item)
} }
for (let i = oldLength; i < newLength; i++) { for (let i = oldLength; i < newLength; i++) {
mount(source, i) mount(source, i)
@ -249,7 +262,7 @@ export const createFor = (
} }
const mount = ( const mount = (
source: any, source: ResolvedSource,
idx: number, idx: number,
anchor: Node | undefined = parentAnchor, anchor: Node | undefined = parentAnchor,
): ForBlock => { ): ForBlock => {
@ -319,55 +332,60 @@ export const createFor = (
} }
export function createForSlots( export function createForSlots(
source: Source, rawSource: Source,
getSlot: (item: any, key: any, index?: number) => DynamicSlot, getSlot: (item: any, key: any, index?: number) => DynamicSlot,
): DynamicSlot[] { ): DynamicSlot[] {
const sourceLength = getLength(source) const source = normalizeSource(rawSource)
const sourceLength = source.values.length
const slots = new Array<DynamicSlot>(sourceLength) const slots = new Array<DynamicSlot>(sourceLength)
for (let i = 0; i < sourceLength; i++) { for (let i = 0; i < sourceLength; i++) {
const [item, key, index] = getItem(source, i) slots[i] = getSlot(...getItem(source, i))
slots[i] = getSlot(item, key, index)
} }
return slots return slots
} }
function getLength(source: any): number { function normalizeSource(source: any): ResolvedSource {
if (isArray(source) || isString(source)) { let values = source
return source.length let needsWrap = false
let keys
if (isArray(source)) {
if (isReactive(source)) {
needsWrap = !isShallow(source)
values = shallowReadArray(source)
}
} else if (isString(source)) {
values = source.split('')
} else if (typeof source === 'number') { } else if (typeof source === 'number') {
if (__DEV__ && !Number.isInteger(source)) { if (__DEV__ && !Number.isInteger(source)) {
warn(`The v-for range expect an integer value but got ${source}.`) warn(`The v-for range expect an integer value but got ${source}.`)
} }
return source values = new Array(source)
for (let i = 0; i < source; i++) values[i] = i + 1
} else if (isObject(source)) { } else if (isObject(source)) {
if (source[Symbol.iterator as any]) { if (source[Symbol.iterator as any]) {
return Array.from(source as Iterable<any>).length values = Array.from(source as Iterable<any>)
} else { } else {
return Object.keys(source).length keys = Object.keys(source)
values = new Array(keys.length)
for (let i = 0, l = keys.length; i < l; i++) {
values[i] = source[keys[i]]
} }
} }
return 0 }
return { values, needsWrap, keys }
} }
function getItem( function getItem(
source: any, { keys, values, needsWrap }: ResolvedSource,
idx: number, idx: number,
): [item: any, key: any, index?: number] { ): [item: any, key: any, index?: number] {
if (isArray(source) || isString(source)) { const value = needsWrap ? toReactive(values[idx]) : values[idx]
return [source[idx], idx, undefined] if (keys) {
} else if (typeof source === 'number') { return [value, keys[idx], idx]
return [idx + 1, idx, undefined]
} else if (isObject(source)) {
if (source[Symbol.iterator as any]) {
source = Array.from(source as Iterable<any>)
return [source[idx], idx, undefined]
} else { } else {
const key = Object.keys(source)[idx] return [value, idx, undefined]
return [source[key], key, idx]
} }
} }
return null!
}
function normalizeAnchor(node: Block): Node { function normalizeAnchor(node: Block): Node {
if (node instanceof Node) { if (node instanceof Node) {