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..bf50a8e82 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", void 0, true) return [n2, () => {}] }) return n0 diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap index ba867bd75..bf50a8e82 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformTemplateRef.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", void 0, true) return [n2, () => {}] }) return n0 diff --git a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts index eeee1adfa..22f68f953 100644 --- a/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/transformTemplateRef.spec.ts @@ -43,7 +43,6 @@ describe('compiler: template ref transform', () => { }, }, }) - expect(code).matchSnapshot() expect(code).contains('_setRef(n0, "foo")') }) @@ -56,21 +55,28 @@ describe('compiler: template ref transform', () => { flags: DynamicFlag.REFERENCED, }) expect(ir.template).toEqual(['
']) - expect(ir.block.operation).lengthOf(1) - expect(ir.block.operation[0]).toMatchObject({ - type: IRNodeTypes.SET_TEMPLATE_REF, - element: 0, - value: { - content: 'foo', - isStatic: false, - loc: { - start: { line: 1, column: 12, offset: 11 }, - end: { line: 1, column: 15, offset: 14 }, - }, + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.DECLARE_OLD_REF, + id: 0, }, - }) + ]) + expect(ir.block.effect).toMatchObject([ + { + operations: [ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 0, + value: { + content: 'foo', + isStatic: false, + }, + }, + ], + }, + ]) expect(code).matchSnapshot() - expect(code).contains('_setRef(n0, _ctx.foo)') + expect(code).contains('_setRef(n0, _ctx.foo, r0)') }) test('ref + v-if', () => { @@ -82,21 +88,17 @@ describe('compiler: template ref transform', () => { expect(ir.block.operation[0].type).toBe(IRNodeTypes.IF) const { positive } = ir.block.operation[0] as IfIRNode - - expect(positive.operation).lengthOf(1) - expect(positive.operation[0]).toMatchObject({ - type: IRNodeTypes.SET_TEMPLATE_REF, - element: 2, - value: { - content: 'foo', - isStatic: true, - loc: { - start: { line: 1, column: 10, offset: 9 }, - end: { line: 1, column: 15, offset: 14 }, + expect(positive.operation).toMatchObject([ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 2, + value: { + content: 'foo', + isStatic: true, }, + effect: false, }, - }) - + ]) expect(code).matchSnapshot() expect(code).contains('_setRef(n2, "foo")') }) @@ -107,21 +109,19 @@ describe('compiler: template ref transform', () => { ) const { render } = ir.block.operation[0] as ForIRNode - expect(render.operation).lengthOf(1) - expect(render.operation[0]).toMatchObject({ - type: IRNodeTypes.SET_TEMPLATE_REF, - element: 2, - value: { - content: 'foo', - isStatic: true, - loc: { - start: { line: 1, column: 10, offset: 9 }, - end: { line: 1, column: 15, offset: 14 }, + expect(render.operation).toMatchObject([ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 2, + value: { + content: 'foo', + isStatic: true, }, + refFor: true, + effect: false, }, - refFor: true, - }) + ]) expect(code).matchSnapshot() - expect(code).contains('_setRef(n2, "foo", true)') + expect(code).contains('_setRef(n2, "foo", void 0, true)') }) }) diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index 224c1fadf..0d1627e78 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -7,7 +7,7 @@ import { genSetHtml } from './html' import { genIf } from './if' import { genSetModelValue } from './modelValue' import { genDynamicProps, genSetProp } from './prop' -import { genSetTemplateRef } from './templateRef' +import { genDeclareOldRef, genSetTemplateRef } from './templateRef' import { genCreateTextNode, genSetText } from './text' import { type CodeFragment, @@ -59,6 +59,8 @@ export function genOperation( return genFor(oper, context) case IRNodeTypes.CREATE_COMPONENT_NODE: return genCreateComponent(oper, context) + case IRNodeTypes.DECLARE_OLD_REF: + return genDeclareOldRef(oper) } return [] diff --git a/packages/compiler-vapor/src/generators/templateRef.ts b/packages/compiler-vapor/src/generators/templateRef.ts index 8c1022d6e..a7fa524bb 100644 --- a/packages/compiler-vapor/src/generators/templateRef.ts +++ b/packages/compiler-vapor/src/generators/templateRef.ts @@ -1,6 +1,6 @@ import { genExpression } from './expression' import type { CodegenContext } from '../generate' -import type { SetTemplateRefIRNode } from '../ir' +import type { DeclareOldRefIRNode, SetTemplateRefIRNode } from '../ir' import { type CodeFragment, NEWLINE, genCall } from './utils' export function genSetTemplateRef( @@ -10,11 +10,17 @@ export function genSetTemplateRef( const { vaporHelper } = context return [ NEWLINE, + oper.effect && `r${oper.element} = `, ...genCall( vaporHelper('setRef'), `n${oper.element}`, genExpression(oper.value, context), + oper.effect ? `r${oper.element}` : oper.refFor ? 'void 0' : undefined, oper.refFor && 'true', ), ] } + +export function genDeclareOldRef(oper: DeclareOldRefIRNode): CodeFragment[] { + return [NEWLINE, `let r${oper.id}`] +} diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index e5ba223d7..4cc427cd9 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -32,6 +32,7 @@ export enum IRNodeTypes { CREATE_COMPONENT_NODE, WITH_DIRECTIVE, + DECLARE_OLD_REF, // consider make it more general IF, FOR, @@ -145,6 +146,7 @@ export interface SetTemplateRefIRNode extends BaseIRNode { element: number value: SimpleExpressionNode refFor: boolean + effect: boolean } export interface SetModelValueIRNode extends BaseIRNode { @@ -194,6 +196,11 @@ export interface CreateComponentIRNode extends BaseIRNode { root: boolean } +export interface DeclareOldRefIRNode extends BaseIRNode { + type: IRNodeTypes.DECLARE_OLD_REF + id: number +} + export type IRNode = OperationNode | RootIRNode export type OperationNode = | SetPropIRNode @@ -211,6 +218,7 @@ export type OperationNode = | IfIRNode | ForIRNode | CreateComponentIRNode + | DeclareOldRefIRNode export enum DynamicFlag { NONE = 0, diff --git a/packages/compiler-vapor/src/transforms/transformTemplateRef.ts b/packages/compiler-vapor/src/transforms/transformTemplateRef.ts index fd0cc0fc8..8dbd9ae73 100644 --- a/packages/compiler-vapor/src/transforms/transformTemplateRef.ts +++ b/packages/compiler-vapor/src/transforms/transformTemplateRef.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 transformTemplateRef: NodeTransform = (node, context) => { @@ -24,11 +24,20 @@ export const transformTemplateRef: NodeTransform = (node, context) => { : EMPTY_EXPRESSION } - return () => - context.registerOperation({ + return () => { + const id = context.reference() + const effect = !isConstantExpression(value) + effect && + context.registerOperation({ + type: IRNodeTypes.DECLARE_OLD_REF, + id, + }) + context.registerEffect([value], { type: IRNodeTypes.SET_TEMPLATE_REF, - element: context.reference(), + element: id, value, refFor: !!context.inVFor, + effect, }) + } } diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index 6dc10eff3..41997b31a 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -1,4 +1,18 @@ -import { ref, setRef, template } from '../../src' +import type { NodeRef } from 'packages/runtime-vapor/src/dom/templateRef' +import { + createFor, + createIf, + getCurrentInstance, + insert, + nextTick, + reactive, + ref, + renderEffect, + setRef, + setText, + template, + watchEffect, +} from '../../src' import { makeRender } from '../_utils' const define = makeRender() @@ -23,4 +37,593 @@ 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) + }) + + it('function ref mount', () => { + const fn = vi.fn() + const t0 = template('
') + const { render } = define({ + render() { + const n0 = t0() + setRef(n0 as Element, fn) + return n0 + }, + }) + + const { host } = render() + expect(fn.mock.calls[0][0]).toBe(host.children[0]) + }) + + it('function ref update', async () => { + const fn1 = vi.fn() + const fn2 = vi.fn() + const fn = ref(fn1) + + const t0 = template('
') + const { render } = define({ + render() { + const n0 = t0() + let r0: NodeRef | undefined + renderEffect(() => { + r0 = setRef(n0 as Element, fn.value, r0) + }) + return n0 + }, + }) + + const { host } = render() + + expect(fn1.mock.calls).toHaveLength(1) + expect(fn1.mock.calls[0][0]).toBe(host.children[0]) + expect(fn2.mock.calls).toHaveLength(0) + + fn.value = fn2 + await nextTick() + expect(fn1.mock.calls).toHaveLength(1) + expect(fn2.mock.calls).toHaveLength(1) + expect(fn2.mock.calls[0][0]).toBe(host.children[0]) + }) + + it('function ref unmount', async () => { + const fn = vi.fn() + const toggle = ref(true) + + const t0 = template('
') + const { render } = define({ + render() { + const n0 = createIf( + () => toggle.value, + () => { + const n1 = t0() + setRef(n1 as Element, fn) + return n1 + }, + ) + return n0 + }, + }) + const { host } = render() + expect(fn.mock.calls[0][0]).toBe(host.children[0]) + toggle.value = false + await nextTick() + expect(fn.mock.calls[1][0]).toBe(undefined) + }) + + it('should work with direct reactive property', () => { + const state = reactive({ + refKey: null, + }) + + const t0 = template('
') + const { render } = define({ + setup() { + return state + }, + render() { + const n0 = t0() + setRef(n0 as Element, 'refKey') + return n0 + }, + }) + const { host } = render() + expect(state.refKey).toBe(host.children[0]) + }) + + test('multiple root refs', () => { + const refKey1 = ref(null) + const refKey2 = ref(null) + const refKey3 = ref(null) + + const t0 = template('
') + const t1 = template('
') + const t2 = template('
') + const { render } = define({ + setup() { + return { + refKey1, + refKey2, + refKey3, + } + }, + render() { + const n0 = t0() + const n1 = t1() + const n2 = t2() + setRef(n0 as Element, 'refKey1') + setRef(n1 as Element, 'refKey2') + setRef(n2 as Element, 'refKey3') + return [n0, n1, n2] + }, + }) + const { host } = render() + // Note: toBe Condition is different from core test case + // Core test case is expecting refKey1.value to be host.children[1] + expect(refKey1.value).toBe(host.children[0]) + expect(refKey2.value).toBe(host.children[1]) + expect(refKey3.value).toBe(host.children[2]) + }) + + // #1505 + test('reactive template ref in the same template', async () => { + const t0 = template('
') + const el = ref() + const { render } = define({ + render() { + const n0 = t0() + setRef(n0 as Element, el) + renderEffect(() => { + setText(n0, el.value && el.value.getAttribute('id')) + }) + return n0 + }, + }) + + const { host } = render() + // ref not ready on first render, but should queue an update immediately + expect(host.innerHTML).toBe(`
`) + await nextTick() + // ref should be updated + expect(host.innerHTML).toBe(`
foo
`) + }) + + // #1834 + test('exchange refs', async () => { + const refToggle = ref(false) + const spy = vi.fn() + + const t0 = template('

') + const t1 = template('') + const { render } = define({ + render() { + const instance = getCurrentInstance()! + const n0 = t0() + const n1 = t1() + let r0: NodeRef | undefined + let r1: NodeRef | undefined + renderEffect(() => { + r0 = setRef(n0 as Element, refToggle.value ? 'foo' : 'bar', r0) + }) + renderEffect(() => { + r1 = setRef(n1 as Element, refToggle.value ? 'bar' : 'foo', r1) + }) + watchEffect( + () => { + refToggle.value + spy( + (instance.refs.foo as HTMLElement).tagName, + (instance.refs.bar as HTMLElement).tagName, + ) + }, + { + flush: 'post', + }, + ) + return [n0, n1] + }, + }) + + render() + + expect(spy.mock.calls[0][0]).toBe('I') + expect(spy.mock.calls[0][1]).toBe('P') + refToggle.value = true + await nextTick() + expect(spy.mock.calls[1][0]).toBe('P') + expect(spy.mock.calls[1][1]).toBe('I') + }) + + // #1789 + test('toggle the same ref to different elements', async () => { + const refToggle = ref(false) + const spy = vi.fn() + + const t0 = template('

') + const t1 = template('') + const { render } = define({ + render() { + const instance = getCurrentInstance()! + const n0 = createIf( + () => refToggle.value, + () => { + const n1 = t0() + setRef(n1 as Element, 'foo') + return n1 + }, + () => { + const n1 = t1() + setRef(n1 as Element, 'foo') + return n1 + }, + ) + watchEffect( + () => { + refToggle.value + spy((instance.refs.foo as HTMLElement).tagName) + }, + { + flush: 'post', + }, + ) + return [n0] + }, + }) + + render() + + expect(spy.mock.calls[0][0]).toBe('I') + refToggle.value = true + await nextTick() + expect(spy.mock.calls[1][0]).toBe('P') + }) + + // compiled output of v-for + template ref + test('ref in v-for', async () => { + const show = ref(true) + const list = reactive([1, 2, 3]) + const listRefs = ref([]) + const mapRefs = () => listRefs.value.map((n: HTMLElement) => n.innerHTML) + + const t0 = template('') + const t1 = template('
  • ') + const { render } = define({ + render() { + const n0 = createIf( + () => show.value, + () => { + const n1 = t0() + const n2 = createFor( + () => list, + _block => { + const n1 = t1() + setRef(n1 as Element, listRefs, undefined, true) + const updateEffect = () => { + const [item] = _block.s + setText(n1, item) + } + renderEffect(updateEffect) + return [n1, updateEffect] + }, + ) + insert(n2, n1 as ParentNode) + return n1 + }, + ) + return n0 + }, + }) + render() + + expect(mapRefs()).toMatchObject(['1', '2', '3']) + + list.push(4) + await nextTick() + expect(mapRefs()).toMatchObject(['1', '2', '3', '4']) + + list.shift() + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + + show.value = !show.value + await nextTick() + + expect(mapRefs()).toMatchObject([]) + + show.value = !show.value + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + }) + + test('named ref in v-for', async () => { + const show = ref(true) + const list = reactive([1, 2, 3]) + const listRefs = ref([]) + const mapRefs = () => listRefs.value.map((n: HTMLElement) => n.innerHTML) + + const t0 = template('') + const t1 = template('
  • ') + const { render } = define({ + setup() { + return { listRefs } + }, + render() { + const n0 = createIf( + () => show.value, + () => { + const n1 = t0() + const n2 = createFor( + () => list, + _block => { + const n1 = t1() + setRef(n1 as Element, 'listRefs', undefined, true) + const updateEffect = () => { + const [item] = _block.s + setText(n1, item) + } + renderEffect(updateEffect) + return [n1, updateEffect] + }, + ) + insert(n2, n1 as ParentNode) + return n1 + }, + ) + return n0 + }, + }) + render() + + expect(mapRefs()).toMatchObject(['1', '2', '3']) + + list.push(4) + await nextTick() + expect(mapRefs()).toMatchObject(['1', '2', '3', '4']) + + list.shift() + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + + show.value = !show.value + await nextTick() + + expect(mapRefs()).toMatchObject([]) + + show.value = !show.value + await nextTick() + expect(mapRefs()).toMatchObject(['2', '3', '4']) + }) + + // #6697 v-for ref behaves differently under production and development + test('named ref in v-for , should be responsive when rendering', async () => { + const list = ref([1, 2, 3]) + const listRefs = ref([]) + + const t0 = template('
    ') + const t1 = template('
  • ') + const { render } = define({ + setup() { + return { listRefs } + }, + render() { + const n0 = t0() + const n1 = n0.firstChild + const n2 = n1!.nextSibling! + const n3 = createFor( + () => list.value, + _block => { + const n4 = t1() + setRef(n4 as Element, 'listRefs', undefined, true) + const updateEffect = () => { + const [item] = _block.s + setText(n4, item) + } + renderEffect(updateEffect) + return [n4, updateEffect] + }, + ) + insert(n3, n2 as unknown as ParentNode) + renderEffect(() => { + setText(n1!, String(listRefs.value)) + }) + return n0 + }, + }) + + const { host } = render() + + await nextTick() + expect(String(listRefs.value)).toBe( + '[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]', + ) + expect(host.innerHTML).toBe( + '
    [object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]
    ', + ) + + list.value.splice(0, 1) + await nextTick() + expect(String(listRefs.value)).toBe( + '[object HTMLLIElement],[object HTMLLIElement]', + ) + expect(host.innerHTML).toBe( + '
    [object HTMLLIElement],[object HTMLLIElement]
    ', + ) + }) + + // TODO: need to implement Component slots + // test('string ref inside slots', async () => { + // const spy = vi.fn() + // const { component: Child } = define({ + // render(this: any) { + // return this.$slots.default() + // }, + // }) + // const { render } = define({ + // render() { + // onMounted(function (this: any) { + // spy(this.$refs.foo.tag) + // }) + // const n0 = createComponent(Child) + // setRef(n0, 'foo') + // return n0 + // }, + // }) + // const { host } = render() + + // expect(spy).toHaveBeenCalledWith('div') + // }) + + //TODO: need setup return render function + // it('render function ref mount', () => { + // const el = ref(null) + + // const Comp = define({ + // setup() { + // return () => h('div', { ref: el }) + // }, + // }) + // render(h(Comp), root) + // expect(el.value).toBe(root.children[0]) + // }) + + // it('render function ref update', async () => { + // const root = nodeOps.createElement('div') + // const refs = { + // foo: ref(null), + // bar: ref(null), + // } + // const refKey = ref('foo') + + // const Comp = { + // setup() { + // return () => h('div', { ref: refs[refKey.value] }) + // }, + // } + // render(h(Comp), root) + // expect(refs.foo.value).toBe(root.children[0]) + // expect(refs.bar.value).toBe(null) + + // refKey.value = 'bar' + // await nextTick() + // expect(refs.foo.value).toBe(null) + // expect(refs.bar.value).toBe(root.children[0]) + // }) + + // it('render function ref unmount', async () => { + // const root = nodeOps.createElement('div') + // const el = ref(null) + // const toggle = ref(true) + + // const Comp = { + // setup() { + // return () => (toggle.value ? h('div', { ref: el }) : null) + // }, + // } + // render(h(Comp), root) + // expect(el.value).toBe(root.children[0]) + + // toggle.value = false + // await nextTick() + // expect(el.value).toBe(null) + // }) + + // TODO: can not reproduce in Vapor + // // #2078 + // test('handling multiple merged refs', async () => { + // const Foo = { + // render: () => h('div', 'foo'), + // } + // const Bar = { + // render: () => h('div', 'bar'), + // } + + // const viewRef = shallowRef(Foo) + // const elRef1 = ref() + // const elRef2 = ref() + + // const App = { + // render() { + // if (!viewRef.value) { + // return null + // } + // const view = h(viewRef.value, { ref: elRef1 }) + // return h(view, { ref: elRef2 }) + // }, + // } + // const root = nodeOps.createElement('div') + // render(h(App), root) + + // expect(serializeInner(elRef1.value.$el)).toBe('foo') + // expect(elRef1.value).toBe(elRef2.value) + + // viewRef.value = Bar + // await nextTick() + // expect(serializeInner(elRef1.value.$el)).toBe('bar') + // expect(elRef1.value).toBe(elRef2.value) + + // viewRef.value = null + // await nextTick() + // expect(elRef1.value).toBeNull() + // expect(elRef1.value).toBe(elRef2.value) + // }) }) 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 }