Skip to content

Commit

Permalink
feat: support more source types for v-for
Browse files Browse the repository at this point in the history
Co-authored-by: wangjie36 <[email protected]>

closes #139
  • Loading branch information
sxzz committed Feb 29, 2024
1 parent d51d558 commit 357c8d0
Show file tree
Hide file tree
Showing 7 changed files with 291 additions and 80 deletions.
2 changes: 1 addition & 1 deletion packages/compiler-vapor/__tests__/transforms/vFor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ describe('compiler: v-for', () => {
children: [{ template: 0 }],
},
},
keyProperty: {
keyProp: {
type: NodeTypes.SIMPLE_EXPRESSION,
content: 'item.id',
},
Expand Down
24 changes: 14 additions & 10 deletions packages/compiler-vapor/src/generators/for.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,19 @@ import {
NEWLINE,
buildCodeFragment,
genCall,
genMulti,
} from './utils'

export function genFor(
oper: ForIRNode,
context: CodegenContext,
): CodeFragment[] {
const { vaporHelper } = context
const { source, value, key, render, keyProperty } = oper
const { source, value, key, index, render, keyProp } = oper

const rawValue = value && value.content
const rawKey = key && key.content
const rawIndex = index && index.content

const sourceExpr = ['() => (', ...genExpression(source, context), ')']
let updateFn = '_updateEffect'
Expand All @@ -30,6 +32,7 @@ export function genFor(
const idMap: Record<string, string> = {}
if (rawValue) idMap[rawValue] = `_block.s[0]`
if (rawKey) idMap[rawKey] = `_block.s[1]`
if (rawIndex) idMap[rawIndex] = `_block.s[2]`

const blockReturns = (returns: CodeFragment[]): CodeFragment[] => [
'[',
Expand All @@ -43,19 +46,20 @@ export function genFor(
)

let getKeyFn: CodeFragment[] | false = false
if (keyProperty) {
if (keyProp) {
const idMap: Record<string, null> = {}
if (rawValue) idMap[rawValue] = null
if (rawKey) idMap[rawKey] = null
const expr = context.withId(
() => genExpression(keyProperty, context),
idMap,
)
if (rawIndex) idMap[rawIndex] = null
const expr = context.withId(() => genExpression(keyProp, context), idMap)
getKeyFn = [
'(',
rawValue ? rawValue : rawKey ? '_' : '',
rawKey && `, ${rawKey}`,
') => (',
...genMulti(
['(', ')', ', '],
rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined,
rawKey ? rawKey : rawIndex ? '__' : undefined,
rawIndex,
),
' => (',
...expr,
')',
]
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export interface ForIRNode extends BaseIRNode {
value?: SimpleExpressionNode
key?: SimpleExpressionNode
index?: SimpleExpressionNode
keyProperty?: SimpleExpressionNode
keyProp?: SimpleExpressionNode
render: BlockIRNode
}

Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-vapor/src/transforms/vFor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function processFor(
value: value as SimpleExpressionNode | undefined,
key: key as SimpleExpressionNode | undefined,
index: index as SimpleExpressionNode | undefined,
keyProperty,
keyProp: keyProperty,
render,
})
}
Expand Down
178 changes: 151 additions & 27 deletions packages/runtime-vapor/__tests__/for.spec.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,187 @@
import {
computed,
createFor,
createTextNode,
nextTick,
ref,
renderEffect,
setText,
} from '../src'
import { createFor, nextTick, ref, renderEffect } from '../src'
import { makeRender } from './_utils'

const define = makeRender()

describe('createFor', () => {
test('basic', async () => {
test('array source', async () => {
const list = ref([{ name: '1' }, { name: '2' }, { name: '3' }])
const sort = ref(false)
const sortedList = computed(() =>
sort.value ? Array.from(list.value).reverse() : list.value,
)
function reverse() {
list.value = list.value.reverse()
}

const { host } = define(() => {
const n1 = createFor(
() => sortedList.value,
() => list.value,
block => {
const n3 = createTextNode()
const span = document.createElement('li')
const update = () => {
const [item] = block.s
setText(n3, item.name)
const [item, key, index] = block.s
span.innerHTML = `${key}. ${item.name}`

// index should be undefined if source is not an object
expect(index).toBe(undefined)
}
renderEffect(update)
return [n3, update]
return [span, update]
},
item => item.name,
)
return [n1]
return n1
}).render()

expect(host.innerHTML).toBe('123<!--for-->')
expect(host.innerHTML).toBe(
'<li>0. 1</li><li>1. 2</li><li>2. 3</li><!--for-->',
)

// add
list.value.push({ name: '4' })
await nextTick()
expect(host.innerHTML).toBe('1234<!--for-->')
expect(host.innerHTML).toBe(
'<li>0. 1</li><li>1. 2</li><li>2. 3</li><li>3. 4</li><!--for-->',
)

// move
sort.value = true
reverse()
await nextTick()
expect(host.innerHTML).toBe('4321<!--for-->')
sort.value = false
expect(host.innerHTML).toBe(
'<li>0. 4</li><li>1. 3</li><li>2. 2</li><li>3. 1</li><!--for-->',
)

reverse()
await nextTick()
expect(host.innerHTML).toBe('1234<!--for-->')
expect(host.innerHTML).toBe(
'<li>0. 1</li><li>1. 2</li><li>2. 3</li><li>3. 4</li><!--for-->',
)

// change
list.value[0].name = 'a'
await nextTick()
expect(host.innerHTML).toBe('a234<!--for-->')
expect(host.innerHTML).toBe(
'<li>0. a</li><li>1. 2</li><li>2. 3</li><li>3. 4</li><!--for-->',
)

// remove
list.value.splice(1, 1)
await nextTick()
expect(host.innerHTML).toBe(
'<li>0. a</li><li>1. 3</li><li>2. 4</li><!--for-->',
)

// clear
list.value = []
await nextTick()
expect(host.innerHTML).toBe('<!--for-->')
})

test('number source', async () => {
const count = ref(3)

const { host } = define(() => {
const n1 = createFor(
() => count.value,
block => {
const span = document.createElement('li')
const update = () => {
const [item, key, index] = block.s
span.innerHTML = `${key}. ${item}`

// index should be undefined if source is not an object
expect(index).toBe(undefined)
}
renderEffect(update)
return [span, update]
},
item => item.name,
)
return n1
}).render()

expect(host.innerHTML).toBe(
'<li>0. 1</li><li>1. 2</li><li>2. 3</li><!--for-->',
)

// add
count.value = 4
await nextTick()
expect(host.innerHTML).toBe(
'<li>0. 1</li><li>1. 2</li><li>2. 3</li><li>3. 4</li><!--for-->',
)

// remove
count.value = 2
await nextTick()
expect(host.innerHTML).toBe('<li>0. 1</li><li>1. 2</li><!--for-->')

// clear
count.value = 0
await nextTick()
expect(host.innerHTML).toBe('<!--for-->')
})

test('object source', async () => {
const initial = () => ({ a: 1, b: 2, c: 3 })
const data = ref<Record<string, number>>(initial())

const { host } = define(() => {
const n1 = createFor(
() => data.value,
block => {
const span = document.createElement('li')
const update = () => {
const [item, key, index] = block.s
span.innerHTML = `${key}${index}. ${item}`
expect(index).not.toBe(undefined)
}
renderEffect(update)
return [span, update]
},
item => {
return item
},
)
return n1
}).render()

expect(host.innerHTML).toBe(
'<li>a0. 1</li><li>b1. 2</li><li>c2. 3</li><!--for-->',
)

// move
data.value = {
c: 3,
b: 2,
a: 1,
}
await nextTick()
expect(host.innerHTML).toBe(
'<li>c0. 3</li><li>b1. 2</li><li>a2. 1</li><!--for-->',
)

// add
data.value.d = 4
await nextTick()
expect(host.innerHTML).toBe(
'<li>c0. 3</li><li>b1. 2</li><li>a2. 1</li><li>d3. 4</li><!--for-->',
)

// change
data.value.b = 100
await nextTick()
expect(host.innerHTML).toBe(
'<li>c0. 3</li><li>b1. 100</li><li>a2. 1</li><li>d3. 4</li><!--for-->',
)

// remove
delete data.value.c
await nextTick()
expect(host.innerHTML).toBe(
'<li>b0. 100</li><li>a1. 1</li><li>d2. 4</li><!--for-->',
)

// clear
data.value = {}
await nextTick()
expect(host.innerHTML).toBe('<!--for-->')
})
})

0 comments on commit 357c8d0

Please sign in to comment.