feat(compiler-vapor): add getKey function for v-for

This commit is contained in:
三咲智子 Kevin Deng 2024-02-08 20:28:10 +08:00
parent 8fb01504da
commit 35b78920c4
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
8 changed files with 69 additions and 22 deletions

View File

@ -14,7 +14,7 @@ export function render(_ctx) {
_setText(n3, item) _setText(n3, item)
}) })
return n2 return n2
}) }, (item) => (item.id))
return [n1] return [n1]
}" }"
`; `;

View File

@ -21,7 +21,7 @@ const compileWithVFor = makeCompile({
describe('compiler: v-for', () => { describe('compiler: v-for', () => {
test('basic v-for', () => { test('basic v-for', () => {
const { code, ir, vaporHelpers, helpers } = compileWithVFor( const { code, ir, vaporHelpers, helpers } = compileWithVFor(
`<div v-for="item of items" @click="remove(item)">{{ item }}</div>`, `<div v-for="item of items" :key="item.id" @click="remove(item)">{{ item }}</div>`,
) )
expect(code).matchSnapshot() expect(code).matchSnapshot()
@ -52,6 +52,10 @@ describe('compiler: v-for', () => {
type: IRNodeTypes.BLOCK_FUNCTION, type: IRNodeTypes.BLOCK_FUNCTION,
templateIndex: 0, templateIndex: 0,
}, },
keyProperty: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item.id',
},
}, },
]) ])
expect(ir.returns).toEqual([1]) expect(ir.returns).toEqual([1])

View File

@ -17,7 +17,7 @@ export function genFor(
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { call, vaporHelper } = context
const { source, value, key, render } = oper const { source, value, key, render, keyProperty } = oper
const rawValue = value && value.content const rawValue = value && value.content
const rawKey = key && key.content const rawKey = key && key.content
@ -34,12 +34,31 @@ export function genFor(
idMap, idMap,
) )
let getKeyFn: CodeFragment[] | false = false
if (keyProperty) {
const idMap: Record<string, null> = {}
if (rawValue) idMap[rawValue] = null
if (rawKey) idMap[rawKey] = null
const expr = context.withId(
() => genExpression(keyProperty, context),
idMap,
)
getKeyFn = [
'(',
rawValue ? rawValue : rawKey ? '_' : '',
rawKey && `, ${rawKey}`,
') => (',
...expr,
')',
]
}
context.genEffect = undefined context.genEffect = undefined
return [ return [
NEWLINE, NEWLINE,
`const n${oper.id} = `, `const n${oper.id} = `,
...call(vaporHelper('createFor'), sourceExpr, blockFn), ...call(vaporHelper('createFor'), sourceExpr, blockFn, getKeyFn),
] ]
function genEffectInFor(effects: IREffect[]): CodeFragment[] { function genEffectInFor(effects: IREffect[]): CodeFragment[] {

View File

@ -76,6 +76,7 @@ export interface ForIRNode extends BaseIRNode {
value?: SimpleExpressionNode value?: SimpleExpressionNode
key?: SimpleExpressionNode key?: SimpleExpressionNode
index?: SimpleExpressionNode index?: SimpleExpressionNode
keyProperty?: SimpleExpressionNode
render: BlockFunctionIRNode render: BlockFunctionIRNode
} }

View File

@ -1,20 +1,16 @@
import { import {
type AttributeNode,
NodeTypes, NodeTypes,
type SimpleExpressionNode, type SimpleExpressionNode,
createSimpleExpression, createSimpleExpression,
findProp,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { EMPTY_EXPRESSION, type NodeTransform } from '../transform' import { EMPTY_EXPRESSION, type NodeTransform } from '../transform'
import { IRNodeTypes, type VaporDirectiveNode } from '../ir' import { IRNodeTypes } from '../ir'
import { normalizeBindShorthand } from './vBind' import { normalizeBindShorthand } from './vBind'
import { findProp } from '../utils'
export const transformRef: NodeTransform = (node, context) => { export const transformRef: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return if (node.type !== NodeTypes.ELEMENT) return
const dir = findProp(node, 'ref', false, true) as const dir = findProp(node, 'ref', false, true)
| VaporDirectiveNode
| AttributeNode
if (!dir) return if (!dir) return
let value: SimpleExpressionNode let value: SimpleExpressionNode

View File

@ -35,19 +35,8 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
let { exp } = dir let { exp } = dir
const arg = dir.arg! const arg = dir.arg!
if (arg.isStatic && isReservedProp(arg.content)) return
if (!exp) exp = normalizeBindShorthand(arg, context) if (!exp) exp = normalizeBindShorthand(arg, context)
let camel = false
if (modifiers.includes('camel')) {
if (arg.isStatic) {
arg.content = camelize(arg.content)
} else {
camel = true
}
}
if (!exp.content.trim()) { if (!exp.content.trim()) {
if (!__BROWSER__) { if (!__BROWSER__) {
// #10280 only error against empty expression in non-browser build // #10280 only error against empty expression in non-browser build
@ -60,6 +49,16 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
exp = createSimpleExpression('', true, loc) exp = createSimpleExpression('', true, loc)
} }
if (arg.isStatic && isReservedProp(arg.content)) return
let camel = false
if (modifiers.includes('camel')) {
if (arg.isStatic) {
arg.content = camelize(arg.content)
} else {
camel = true
}
}
return { return {
key: arg, key: arg,
value: exp, value: exp,

View File

@ -18,6 +18,7 @@ import {
type VaporDirectiveNode, type VaporDirectiveNode,
} from '../ir' } from '../ir'
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
import { findProp, propToExpression } from '../utils'
export const transformVFor = createStructuralDirectiveTransform( export const transformVFor = createStructuralDirectiveTransform(
'for', 'for',
@ -45,6 +46,8 @@ export function processFor(
const { source, value, key, index } = parseResult const { source, value, key, index } = parseResult
const keyProp = findProp(node, 'key')
const keyProperty = keyProp && propToExpression(keyProp)
context.node = node = wrapTemplate(node, ['for']) context.node = node = wrapTemplate(node, ['for'])
context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT context.dynamic.flags |= DynamicFlag.NON_TEMPLATE | DynamicFlag.INSERT
const id = context.reference() const id = context.reference()
@ -71,6 +74,7 @@ export function processFor(
value: value as SimpleExpressionNode | undefined, value: value as SimpleExpressionNode | undefined,
key: key as SimpleExpressionNode | undefined, key: key as SimpleExpressionNode | undefined,
index: index as SimpleExpressionNode | undefined, index: index as SimpleExpressionNode | undefined,
keyProperty,
render, render,
}) })
} }

View File

@ -0,0 +1,24 @@
import {
type AttributeNode,
type ElementNode,
NodeTypes,
findProp as _findProp,
createSimpleExpression,
} from '@vue/compiler-dom'
import type { VaporDirectiveNode } from './ir'
import { EMPTY_EXPRESSION } from './transform'
export const findProp = _findProp as (
node: ElementNode,
name: string,
dynamicOnly?: boolean,
allowEmpty?: boolean,
) => AttributeNode | VaporDirectiveNode | undefined
export function propToExpression(prop: AttributeNode | VaporDirectiveNode) {
return prop.type === NodeTypes.ATTRIBUTE
? prop.value
? createSimpleExpression(prop.value.content, true, prop.value.loc)
: EMPTY_EXPRESSION
: prop.exp
}