Skip to content

Commit

Permalink
feat: v-memo
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed Sep 18, 2024
1 parent cc58f65 commit e42c9c0
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 70 deletions.
1 change: 1 addition & 0 deletions benchmark/client/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ const isSelected = createSelector(selected)
v-for="row of rows"
:key="row.id"
:class="{ danger: isSelected(row.id) }"
v-memo="[row.label, row.id === selected]"
>
<td>{{ row.id }}</td>
<td>
Expand Down
139 changes: 76 additions & 63 deletions packages/compiler-vapor/src/generators/for.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { walkIdentifiers } from '@vue/compiler-dom'
import { type SimpleExpressionNode, walkIdentifiers } from '@vue/compiler-dom'
import { genBlock } from './block'
import { genExpression } from './expression'
import type { CodegenContext } from '../generate'
Expand All @@ -16,75 +16,21 @@ export function genFor(
context: CodegenContext,
): CodeFragment[] {
const { vaporHelper } = context
const { source, value, key, index, render, keyProp, once, id } = oper
const { source, value, key, index, render, keyProp, once, id, memo } = oper

let isDestructureAssignment = false
let rawValue: string | null = null
const rawKey = key && key.content
const rawIndex = index && index.content

const sourceExpr = ['() => (', ...genExpression(source, context), ')']

const idsOfValue = new Set<string>()
if (value) {
rawValue = value && value.content
if ((isDestructureAssignment = !!value.ast)) {
walkIdentifiers(
value.ast,
(id, _, __, ___, isLocal) => {
if (isLocal) idsOfValue.add(id.name)
},
true,
)
} else {
idsOfValue.add(rawValue)
}
}

const [depth, exitScope] = context.enterScope()
let propsName: string
const idMap: Record<string, string | null> = {}
if (context.options.prefixIdentifiers) {
propsName = `_ctx${depth}`
Array.from(idsOfValue).forEach(
(id, idIndex) => (idMap[id] = `${propsName}[${idIndex}].value`),
)
if (rawKey) idMap[rawKey] = `${propsName}[${idsOfValue.size}].value`
if (rawIndex) idMap[rawIndex] = `${propsName}[${idsOfValue.size + 1}].value`
} else {
propsName = `[${[rawValue || ((rawKey || rawIndex) && '_'), rawKey || (rawIndex && '__'), rawIndex].filter(Boolean).join(', ')}]`
}

let blockFn = context.withId(
() => genBlock(render, context, [propsName]),
idMap,
)
exitScope()

let getKeyFn: CodeFragment[] | false = false
if (keyProp) {
const idMap: Record<string, null> = {}
if (rawKey) idMap[rawKey] = null
if (rawIndex) idMap[rawIndex] = null
idsOfValue.forEach(id => (idMap[id] = null))

const expr = context.withId(() => genExpression(keyProp, context), idMap)
getKeyFn = [
...genMulti(
['(', ')', ', '],
rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined,
rawKey ? rawKey : rawIndex ? '__' : undefined,
rawIndex,
),
' => (',
...expr,
')',
]
}
const idsInValue = getIdsInValue()
let blockFn = genBlockFn()
const simpleIdMap: Record<string, null> = genSimpleIdMap()

if (isDestructureAssignment) {
const idMap: Record<string, null> = {}
idsOfValue.forEach(id => (idMap[id] = null))
idsInValue.forEach(id => (idMap[id] = null))
if (rawKey) idMap[rawKey] = null
if (rawIndex) idMap[rawIndex] = null
const destructureAssignmentFn: CodeFragment[] = [
Expand All @@ -96,7 +42,7 @@ export function genFor(
rawIndex,
),
') => ',
...genMulti(DELIMITERS_ARRAY, ...idsOfValue, rawKey, rawIndex),
...genMulti(DELIMITERS_ARRAY, ...idsInValue, rawKey, rawIndex),
]

blockFn = genCall(
Expand All @@ -113,10 +59,77 @@ export function genFor(
vaporHelper('createFor'),
sourceExpr,
blockFn,
getKeyFn,
false, // todo: getMemo
genCallback(keyProp),
genCallback(memo),
false, // todo: hydrationNode
once && 'true',
),
]

function getIdsInValue() {
const idsInValue = new Set<string>()
if (value) {
rawValue = value && value.content
if ((isDestructureAssignment = !!value.ast)) {
walkIdentifiers(
value.ast,
(id, _, __, ___, isLocal) => {
if (isLocal) idsInValue.add(id.name)
},
true,
)
} else {
idsInValue.add(rawValue)
}
}
return idsInValue
}

function genBlockFn() {
const [depth, exitScope] = context.enterScope()
let propsName: string
const idMap: Record<string, string | null> = {}
if (context.options.prefixIdentifiers) {
propsName = `_ctx${depth}`
Array.from(idsInValue).forEach(
(id, idIndex) => (idMap[id] = `${propsName}[${idIndex}].value`),
)
if (rawKey) idMap[rawKey] = `${propsName}[${idsInValue.size}].value`
if (rawIndex)
idMap[rawIndex] = `${propsName}[${idsInValue.size + 1}].value`
} else {
propsName = `[${[rawValue || ((rawKey || rawIndex) && '_'), rawKey || (rawIndex && '__'), rawIndex].filter(Boolean).join(', ')}]`
}

const blockFn = context.withId(
() => genBlock(render, context, [propsName]),
idMap,
)
exitScope()
return blockFn
}

function genSimpleIdMap() {
const idMap: Record<string, null> = {}
if (rawKey) idMap[rawKey] = null
if (rawIndex) idMap[rawIndex] = null
idsInValue.forEach(id => (idMap[id] = null))
return idMap
}

function genCallback(expr: SimpleExpressionNode | undefined) {
if (!expr) return false
const res = context.withId(() => genExpression(expr, context), simpleIdMap)
return [
...genMulti(
['(', ')', ', '],
rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined,
rawKey ? rawKey : rawIndex ? '__' : undefined,
rawIndex,
),
' => (',
...res,
')',
]
}
}
1 change: 1 addition & 0 deletions packages/compiler-vapor/src/ir/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export interface IRFor {
value?: SimpleExpressionNode
key?: SimpleExpressionNode
index?: SimpleExpressionNode
memo?: SimpleExpressionNode
}

export interface ForIRNode extends BaseIRNode, IRFor {
Expand Down
4 changes: 3 additions & 1 deletion packages/compiler-vapor/src/transforms/vFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
IRNodeTypes,
type VaporDirectiveNode,
} from '../ir'
import { findProp, propToExpression } from '../utils'
import { findDir, findProp, propToExpression } from '../utils'
import { newBlock, wrapTemplate } from './utils'

export const transformVFor: NodeTransform = createStructuralDirectiveTransform(
Expand Down Expand Up @@ -45,6 +45,7 @@ export function processFor(
const { source, value, key, index } = parseResult

const keyProp = findProp(node, 'key')
const memo = findDir(node, 'memo')
const keyProperty = keyProp && propToExpression(keyProp)
context.node = node = wrapTemplate(node, ['for'])
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
Expand All @@ -65,6 +66,7 @@ export function processFor(
keyProp: keyProperty,
render,
once: context.inVOnce,
memo: memo && memo.exp,
})
}
}
9 changes: 4 additions & 5 deletions packages/runtime-vapor/src/apiCreateFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export const createFor = (
}
}

if (needsUpdate) setState(block, newItem, newKey, newIndex)
if (needsUpdate) updateState(block, newItem, newKey, newIndex)
}

function updateWithoutMemo(
Expand All @@ -321,9 +321,8 @@ export const createFor = (
newKey !== key.value ||
newIndex !== index.value ||
// shallowRef list
(!isReactive(newItem) && isObject(newItem))

if (needsUpdate) setState(block, newItem, newKey, newIndex)
(isObject(newItem) && !isReactive(newItem))
if (needsUpdate) updateState(block, newItem, newKey, newIndex)
}

function unmount({ nodes, scope }: ForBlock) {
Expand All @@ -332,7 +331,7 @@ export const createFor = (
}
}

function setState(
function updateState(
block: ForBlock,
newItem: any,
newKey: any,
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-vapor/src/componentMetadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function getMetadata(
export function recordPropMetadata(el: Node, key: string, value: any): any {
const metadata = getMetadata(el)[MetadataKind.prop]
const prev = metadata[key]
metadata[key] = value
if (prev !== value) metadata[key] = value
return prev
}

Expand Down

0 comments on commit e42c9c0

Please sign in to comment.