Skip to content

Commit c30a343

Browse files
committed
feat: cleanup selector during vFor item unmount
1 parent 3205af5 commit c30a343

File tree

7 files changed

+89
-60
lines changed

7 files changed

+89
-60
lines changed

Diff for: packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap

+4-3
Original file line numberDiff line numberDiff line change
@@ -120,18 +120,19 @@ exports[`compiler: v-for > selector pattern 1`] = `
120120
const t0 = _template("<tr> </tr>", true)
121121
122122
export function render(_ctx) {
123-
const _selector0 = _useSelectorPattern(() => _ctx.selected, (..._args) => {
123+
const _selector0_0 = _useSelectorPattern(() => _ctx.selected, (..._args) => {
124124
_setClass(_args[0], _args[1])
125125
}, 'danger', '')
126126
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
127-
_selector0.register(_for_item0.value.id, _for_item0)
127+
const _cleanup0 = _selector0_0.register(_for_item0.value.id, _for_item0)
128128
const n2 = t0()
129129
const x2 = _child(n2)
130-
_renderEffect(() => {
130+
const _e = _renderEffect(() => {
131131
const _row = _for_item0.value
132132
const _row_id = _row.id
133133
_setText(x2, _toDisplayString(_row_id))
134134
})
135+
_e.cleanup = () => _cleanup0()
135136
return n2
136137
}, (row) => (row.id))
137138
return n0

Diff for: packages/compiler-vapor/src/generators/for.ts

+11-6
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { genBlockContent } from './block'
88
import { genExpression } from './expression'
99
import type { CodegenContext } from '../generate'
10-
import type { BlockIRNode, ForIRNode, IREffect } from '../ir'
10+
import type { BlockIRNode, ForIRNode, IREffect, SetPropIRNode } from '../ir'
1111
import {
1212
type CodeFragment,
1313
INDENT_END,
@@ -16,7 +16,7 @@ import {
1616
genCall,
1717
genMulti,
1818
} from './utils'
19-
import { isNodesEquivalent, type Identifier } from '@babel/types'
19+
import { type Identifier, isNodesEquivalent } from '@babel/types'
2020
import { parseExpression } from '@babel/parser'
2121
import { VaporVForFlags } from '../../../shared/src/vaporFlags'
2222
import { getRuntimeHelper } from './prop'
@@ -92,13 +92,13 @@ export function genFor(
9292

9393
for (let i = 0; i < selectorPatterns.length; i++) {
9494
const { selector, trueValue, falseValue, effect } = selectorPatterns[i]
95-
const selectorName = `_selector${i}`
95+
const selectorName = `_selector${id}_${i}`
9696
const args: CodeFragment[] = [
9797
`() => `,
9898
...genExpression(selector, context),
9999
`, (..._args) => {`,
100100
]
101-
for (const oper of effect.operations) {
101+
for (const oper of effect.operations as SetPropIRNode[]) {
102102
const resolvedHelper = getRuntimeHelper(
103103
oper.tag,
104104
oper.prop.key.content,
@@ -123,13 +123,17 @@ export function genFor(
123123
for (let i = 0; i < selectorPatterns.length; i++) {
124124
frag.push(
125125
NEWLINE,
126-
`_selector${i}.register(`,
126+
`const _cleanup${id} = _selector${id}_${i}.register(`,
127127
...genExpression(keyProp!, context),
128128
`, `,
129129
itemVar,
130130
`)`,
131131
)
132132
}
133+
if (selectorPatterns.length) {
134+
// save cleanup function to the first effect
135+
render.effect[0].cleanup = [`_cleanup${id}()`] as CodeFragment[]
136+
}
133137
frag.push(...genBlockContent(render, context))
134138
frag.push(INDENT_END, NEWLINE, '}')
135139
return frag
@@ -333,7 +337,8 @@ function matchSelectorPattern(
333337
if (
334338
keyProp !== undefined &&
335339
typeof ast === 'object' &&
336-
ast?.type === 'ConditionalExpression' &&
340+
ast &&
341+
ast.type === 'ConditionalExpression' &&
337342
ast.test.type === 'BinaryExpression' &&
338343
ast.test.operator === '===' &&
339344
ast.test.left.type !== 'PrivateName' &&

Diff for: packages/compiler-vapor/src/generators/operation.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,18 @@ export function genEffects(
128128
}
129129

130130
if (effects.length) {
131-
unshift(NEWLINE, `${helper('renderEffect')}(() => `)
131+
// cleanup is only used for `vFor` + `useSelectorPattern`
132+
// the cleanup code fragment can be found in the first effect
133+
const cleanup = effects.length === 1 && effects[0].cleanup
134+
unshift(
135+
NEWLINE,
136+
cleanup ? `const _e = ` : '',
137+
`${helper('renderEffect')}(() => `,
138+
)
132139
push(`)`)
140+
if (cleanup) {
141+
push(NEWLINE, `_e.cleanup = () => `, ...cleanup)
142+
}
133143
}
134144

135145
return frag

Diff for: packages/compiler-vapor/src/ir/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
import type { Prettify } from '@vue/shared'
99
import type { DirectiveTransform, NodeTransform } from '../transform'
1010
import type { IRProp, IRProps, IRSlots } from './component'
11+
import type { CodeFragment } from '..'
1112

1213
export * from './component'
1314

@@ -266,6 +267,7 @@ export interface IRDynamicInfo {
266267
export interface IREffect {
267268
expressions: SimpleExpressionNode[]
268269
operations: OperationNode[]
270+
cleanup?: CodeFragment[]
269271
}
270272

271273
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> &

Diff for: packages/runtime-vapor/src/apiCreateFor.ts

+49
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import {
33
type ShallowRef,
44
isReactive,
55
isShallow,
6+
onScopeDispose,
67
pauseTracking,
78
resetTracking,
89
shallowReadArray,
910
shallowRef,
1011
toReactive,
12+
watch,
1113
} from '@vue/reactivity'
1214
import { getSequence, isArray, isObject, isString } from '@vue/shared'
1315
import { createComment, createTextNode } from './dom/node'
@@ -451,3 +453,50 @@ export function getRestElement(val: any, keys: string[]): any {
451453
export function getDefaultValue(val: any, defaultVal: any): any {
452454
return val === undefined ? defaultVal : val
453455
}
456+
457+
export function useSelectorPattern(
458+
getActiveKey: () => any,
459+
setter: (element: any, value: any) => void,
460+
trueValue: any,
461+
falseValue: any,
462+
): {
463+
register: (key: any, element: any) => () => void
464+
} {
465+
const nodeMap = new Map()
466+
let trueElements: any[] | undefined
467+
468+
onScopeDispose(() => nodeMap.clear(), true)
469+
470+
watch(getActiveKey, newValue => {
471+
if (trueElements !== undefined) {
472+
for (const n of trueElements) {
473+
setter(n, falseValue)
474+
}
475+
}
476+
trueElements = nodeMap.get(newValue)
477+
if (trueElements !== undefined) {
478+
for (const n of trueElements) {
479+
setter(n, trueValue)
480+
}
481+
}
482+
})
483+
484+
return { register }
485+
486+
function register(key: any, element: any): () => void {
487+
const isTrue = getActiveKey() === key
488+
setter(element, isTrue ? trueValue : falseValue)
489+
let elements = nodeMap.get(key)
490+
if (elements !== undefined) {
491+
elements.push(element)
492+
} else {
493+
elements = [element]
494+
nodeMap.set(key, elements)
495+
if (isTrue) {
496+
trueElements = elements
497+
}
498+
}
499+
500+
return () => nodeMap.delete(key)
501+
}
502+
}

Diff for: packages/runtime-vapor/src/index.ts

+1-45
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export {
3737
createForSlots,
3838
getRestElement,
3939
getDefaultValue,
40+
useSelectorPattern,
4041
} from './apiCreateFor'
4142
export { createTemplateRefSetter } from './apiTemplateRef'
4243
export { createDynamicComponent } from './apiCreateDynamicComponent'
@@ -54,48 +55,3 @@ export { withVaporDirectives } from './directives/custom'
5455
* @internal
5556
*/
5657
export { simpleSetCurrentInstance } from '@vue/runtime-core'
57-
58-
import { watch } from '@vue/reactivity'
59-
60-
export function useSelectorPattern(
61-
getActiveKey: () => any,
62-
setter: (element: any, value: any) => void,
63-
trueValue: any,
64-
falseValue: any,
65-
): {
66-
register: (key: any, element: any) => void
67-
} {
68-
const nodeMap = new Map()
69-
let trueElements: any[] | undefined
70-
71-
watch(getActiveKey, newValue => {
72-
if (trueElements !== undefined) {
73-
for (const n of trueElements) {
74-
setter(n, falseValue)
75-
}
76-
}
77-
trueElements = nodeMap.get(newValue)
78-
if (trueElements !== undefined) {
79-
for (const n of trueElements) {
80-
setter(n, trueValue)
81-
}
82-
}
83-
})
84-
85-
return { register }
86-
87-
function register(key: any, element: any): void {
88-
const isTrue = getActiveKey() === key
89-
setter(element, isTrue ? trueValue : falseValue)
90-
let elements = nodeMap.get(key)
91-
if (elements !== undefined) {
92-
elements.push(element)
93-
} else {
94-
elements = [element]
95-
nodeMap.set(key, elements)
96-
if (isTrue) {
97-
trueElements = elements
98-
}
99-
}
100-
}
101-
}

Diff for: packages/runtime-vapor/src/renderEffect.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class RenderEffect extends ReactiveEffect {
5858
// TODO recurse handling
5959
}
6060

61-
callback() {
61+
callback(): void {
6262
const instance = this.i!
6363
const scope = this.scope
6464
// renderEffect is always called after user has registered all hooks
@@ -84,7 +84,7 @@ class RenderEffect extends ReactiveEffect {
8484
}
8585
}
8686

87-
scheduler() {
87+
scheduler(): void {
8888
queueJob(this.baseJob)
8989
}
9090
}
@@ -99,10 +99,16 @@ class RenderEffect_NoLifecycle extends RenderEffect {
9999
}
100100
}
101101

102-
export function renderEffect(fn: () => void, noLifecycle = false): void {
102+
export function renderEffect(
103+
fn: () => void,
104+
noLifecycle = false,
105+
): RenderEffect {
106+
let effect
103107
if (noLifecycle) {
104-
new RenderEffect_NoLifecycle(fn).run()
108+
effect = new RenderEffect_NoLifecycle(fn)
105109
} else {
106-
new RenderEffect(fn).run()
110+
effect = new RenderEffect(fn)
107111
}
112+
effect.run()
113+
return effect
108114
}

0 commit comments

Comments
 (0)