diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap index ba867bd75..cc0147649 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformRef.spec.ts.snap @@ -1,12 +1,13 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: template ref transform > dynamic ref 1`] = ` -"import { setRef as _setRef, template as _template } from 'vue/vapor'; +"import { renderEffect as _renderEffect, setRef as _setRef, template as _template } from 'vue/vapor'; const t0 = _template("
") export function render(_ctx) { const n0 = t0() - _setRef(n0, _ctx.foo) + let r0 + _renderEffect(() => r0 = _setRef(n0, _ctx.foo, r0)) return n0 }" `; @@ -18,7 +19,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = _createFor(() => ([1,2,3]), (_block) => { const n2 = t0() - _setRef(n2, "foo", true) + _setRef(n2, "foo", undefined, true) return [n2, () => {}] }) return n0 @@ -32,7 +33,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = _createIf(() => (true), () => { const n2 = t0() - _setRef(n2, "foo") + _setRef(n2, "foo", undefined) return n2 }) return n0 @@ -45,7 +46,7 @@ const t0 = _template("
") export function render(_ctx) { const n0 = t0() - _setRef(n0, "foo") + _setRef(n0, "foo", undefined) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts index 78b7694ad..83d19816f 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformRef.spec.ts @@ -45,7 +45,7 @@ describe('compiler: template ref transform', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n0, "foo")') + expect(code).contains('_setRef(n0, "foo", undefined)') }) test('dynamic ref', () => { @@ -70,7 +70,7 @@ describe('compiler: template ref transform', () => { }, }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n0, _ctx.foo)') + expect(code).contains('_setRef(n0, _ctx.foo, r0)') }) test('ref + v-if', () => { @@ -98,7 +98,7 @@ describe('compiler: template ref transform', () => { }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n2, "foo")') + expect(code).contains('_setRef(n2, "foo", undefined)') }) test('ref + v-for', () => { @@ -122,6 +122,6 @@ describe('compiler: template ref transform', () => { refFor: true, }) expect(code).matchSnapshot() - expect(code).contains('_setRef(n2, "foo", true)') + expect(code).contains('_setRef(n2, "foo", undefined, true)') }) }) diff --git a/packages/compiler-vapor/src/generators/ref.ts b/packages/compiler-vapor/src/generators/ref.ts index 248221844..753a861bf 100644 --- a/packages/compiler-vapor/src/generators/ref.ts +++ b/packages/compiler-vapor/src/generators/ref.ts @@ -8,13 +8,21 @@ export function genSetRef( context: CodegenContext, ): CodeFragment[] { const { vaporHelper } = context + const dynamicExp = oper.refCount !== -1 return [ NEWLINE, + dynamicExp && `let r${oper.refCount}`, + dynamicExp && NEWLINE, + ...(!!dynamicExp + ? [`${vaporHelper('renderEffect')}(() => `, `r${oper.refCount} = `] + : []), ...genCall( vaporHelper('setRef'), [`n${oper.element}`], genExpression(oper.value, context), + [dynamicExp ? `r${oper.refCount}` : 'undefined'], oper.refFor && 'true', ), + !!dynamicExp && ')', ] } diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 40ccf3bf1..5a68ce0fb 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -145,6 +145,7 @@ export interface SetRefIRNode extends BaseIRNode { element: number value: SimpleExpressionNode refFor: boolean + refCount: number } export interface SetModelValueIRNode extends BaseIRNode { diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index bc2de550a..27aa62d2f 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -79,6 +79,8 @@ export interface TransformContext { component: Set + refCount: number + enterBlock(ir: TransformContext['block'], isVFor?: boolean): () => void reference(): number increaseId(): number @@ -152,6 +154,7 @@ function createRootContext( inVFor: 0, comment: [], component: root.component, + refCount: 0, increaseId: () => globalId++, reference() { diff --git a/packages/compiler-vapor/src/transforms/transformRef.ts b/packages/compiler-vapor/src/transforms/transformRef.ts index 7da99ebfc..402999026 100644 --- a/packages/compiler-vapor/src/transforms/transformRef.ts +++ b/packages/compiler-vapor/src/transforms/transformRef.ts @@ -6,7 +6,7 @@ import { import type { NodeTransform } from '../transform' import { IRNodeTypes } from '../ir' import { normalizeBindShorthand } from './vBind' -import { findProp } from '../utils' +import { findProp, isConstantExpression } from '../utils' import { EMPTY_EXPRESSION } from './utils' export const transformRef: NodeTransform = (node, context) => { @@ -24,11 +24,14 @@ export const transformRef: NodeTransform = (node, context) => { : EMPTY_EXPRESSION } - return () => + return () => { + const dynamicExp = !isConstantExpression(value) context.registerOperation({ type: IRNodeTypes.SET_REF, element: context.reference(), value, refFor: !!context.inVFor, + refCount: dynamicExp ? context.refCount++ : -1, }) + } } diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index 6dc10eff3..7ae406723 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -1,4 +1,12 @@ -import { ref, setRef, template } from '../../src' +import type { NodeRef } from 'packages/runtime-vapor/src/dom/templateRef' +import { + createIf, + nextTick, + ref, + renderEffect, + setRef, + template, +} from '../../src' import { makeRender } from '../_utils' const define = makeRender() @@ -23,4 +31,67 @@ describe('api: template ref', () => { const { host } = render() expect(el.value).toBe(host.children[0]) }) + + it('string ref update', async () => { + const t0 = template('
') + const fooEl = ref(null) + const barEl = ref(null) + const refKey = ref('foo') + + const { render } = define({ + setup() { + return { + foo: fooEl, + bar: barEl, + } + }, + render() { + const n0 = t0() + let r0: NodeRef | undefined + renderEffect(() => { + r0 = setRef(n0 as Element, refKey.value, r0) + }) + return n0 + }, + }) + const { host } = render() + expect(fooEl.value).toBe(host.children[0]) + expect(barEl.value).toBe(null) + + refKey.value = 'bar' + await nextTick() + expect(barEl.value).toBe(host.children[0]) + expect(fooEl.value).toBe(null) + }) + + it('string ref unmount', async () => { + const t0 = template('
') + const el = ref(null) + const toggle = ref(true) + + const { render } = define({ + setup() { + return { + refKey: el, + } + }, + render() { + const n0 = createIf( + () => toggle.value, + () => { + const n1 = t0() + setRef(n1 as Element, 'refKey') + return n1 + }, + ) + return n0 + }, + }) + const { host } = render() + expect(el.value).toBe(host.children[0]) + + toggle.value = false + await nextTick() + expect(el.value).toBe(null) + }) }) diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts index 25a9b014b..b3770739a 100644 --- a/packages/runtime-vapor/src/dom/templateRef.ts +++ b/packages/runtime-vapor/src/dom/templateRef.ts @@ -27,7 +27,12 @@ export type RefEl = Element | ComponentInternalInstance /** * Function for handling a template ref */ -export function setRef(el: RefEl, ref: NodeRef, refFor = false) { +export function setRef( + el: RefEl, + ref: NodeRef, + oldRef?: NodeRef, + refFor = false, +) { if (!currentInstance) return const { setupState, isUnmounted } = currentInstance @@ -42,6 +47,18 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { ? (currentInstance.refs = {}) : currentInstance.refs + // dynamic ref changed. unset old ref + if (oldRef != null && oldRef !== ref) { + if (isString(oldRef)) { + refs[oldRef] = null + if (hasOwn(setupState, oldRef)) { + setupState[oldRef] = null + } + } else if (isRef(oldRef)) { + oldRef.value = null + } + } + if (isFunction(ref)) { const invokeRefSetter = (value?: Element | Record) => { callWithErrorHandling( @@ -117,4 +134,5 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { warn('Invalid template ref type:', ref, `(${typeof ref})`) } } + return ref }