mirror of https://github.com/vuejs/core.git
feat: implement setRef update (#191)
Co-authored-by: 三咲智子 Kevin Deng <sxzz@sxzz.moe>
This commit is contained in:
parent
8dea04bd7f
commit
69580515d9
|
@ -1,12 +1,13 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`compiler: template ref transform > dynamic ref 1`] = `
|
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("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
_setRef(n0, _ctx.foo)
|
let r0
|
||||||
|
_renderEffect(() => r0 = _setRef(n0, _ctx.foo, r0))
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -18,7 +19,7 @@ const t0 = _template("<div></div>")
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = _createFor(() => ([1,2,3]), (_block) => {
|
const n0 = _createFor(() => ([1,2,3]), (_block) => {
|
||||||
const n2 = t0()
|
const n2 = t0()
|
||||||
_setRef(n2, "foo", true)
|
_setRef(n2, "foo", void 0, true)
|
||||||
return [n2, () => {}]
|
return [n2, () => {}]
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`compiler: template ref transform > dynamic ref 1`] = `
|
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("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
_setRef(n0, _ctx.foo)
|
let r0
|
||||||
|
_renderEffect(() => r0 = _setRef(n0, _ctx.foo, r0))
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -18,7 +19,7 @@ const t0 = _template("<div></div>")
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = _createFor(() => ([1,2,3]), (_block) => {
|
const n0 = _createFor(() => ([1,2,3]), (_block) => {
|
||||||
const n2 = t0()
|
const n2 = t0()
|
||||||
_setRef(n2, "foo", true)
|
_setRef(n2, "foo", void 0, true)
|
||||||
return [n2, () => {}]
|
return [n2, () => {}]
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
|
|
|
@ -43,7 +43,6 @@ describe('compiler: template ref transform', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
expect(code).contains('_setRef(n0, "foo")')
|
expect(code).contains('_setRef(n0, "foo")')
|
||||||
})
|
})
|
||||||
|
@ -56,21 +55,28 @@ describe('compiler: template ref transform', () => {
|
||||||
flags: DynamicFlag.REFERENCED,
|
flags: DynamicFlag.REFERENCED,
|
||||||
})
|
})
|
||||||
expect(ir.template).toEqual(['<div></div>'])
|
expect(ir.template).toEqual(['<div></div>'])
|
||||||
expect(ir.block.operation).lengthOf(1)
|
expect(ir.block.operation).toMatchObject([
|
||||||
expect(ir.block.operation[0]).toMatchObject({
|
{
|
||||||
|
type: IRNodeTypes.DECLARE_OLD_REF,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(ir.block.effect).toMatchObject([
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
type: IRNodeTypes.SET_TEMPLATE_REF,
|
type: IRNodeTypes.SET_TEMPLATE_REF,
|
||||||
element: 0,
|
element: 0,
|
||||||
value: {
|
value: {
|
||||||
content: 'foo',
|
content: 'foo',
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
loc: {
|
|
||||||
start: { line: 1, column: 12, offset: 11 },
|
|
||||||
end: { line: 1, column: 15, offset: 14 },
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
expect(code).contains('_setRef(n0, _ctx.foo)')
|
expect(code).contains('_setRef(n0, _ctx.foo, r0)')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('ref + v-if', () => {
|
test('ref + v-if', () => {
|
||||||
|
@ -82,21 +88,17 @@ describe('compiler: template ref transform', () => {
|
||||||
expect(ir.block.operation[0].type).toBe(IRNodeTypes.IF)
|
expect(ir.block.operation[0].type).toBe(IRNodeTypes.IF)
|
||||||
|
|
||||||
const { positive } = ir.block.operation[0] as IfIRNode
|
const { positive } = ir.block.operation[0] as IfIRNode
|
||||||
|
expect(positive.operation).toMatchObject([
|
||||||
expect(positive.operation).lengthOf(1)
|
{
|
||||||
expect(positive.operation[0]).toMatchObject({
|
|
||||||
type: IRNodeTypes.SET_TEMPLATE_REF,
|
type: IRNodeTypes.SET_TEMPLATE_REF,
|
||||||
element: 2,
|
element: 2,
|
||||||
value: {
|
value: {
|
||||||
content: 'foo',
|
content: 'foo',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
loc: {
|
|
||||||
start: { line: 1, column: 10, offset: 9 },
|
|
||||||
end: { line: 1, column: 15, offset: 14 },
|
|
||||||
},
|
},
|
||||||
|
effect: false,
|
||||||
},
|
},
|
||||||
})
|
])
|
||||||
|
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
expect(code).contains('_setRef(n2, "foo")')
|
expect(code).contains('_setRef(n2, "foo")')
|
||||||
})
|
})
|
||||||
|
@ -107,21 +109,19 @@ describe('compiler: template ref transform', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
const { render } = ir.block.operation[0] as ForIRNode
|
const { render } = ir.block.operation[0] as ForIRNode
|
||||||
expect(render.operation).lengthOf(1)
|
expect(render.operation).toMatchObject([
|
||||||
expect(render.operation[0]).toMatchObject({
|
{
|
||||||
type: IRNodeTypes.SET_TEMPLATE_REF,
|
type: IRNodeTypes.SET_TEMPLATE_REF,
|
||||||
element: 2,
|
element: 2,
|
||||||
value: {
|
value: {
|
||||||
content: 'foo',
|
content: 'foo',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
loc: {
|
|
||||||
start: { line: 1, column: 10, offset: 9 },
|
|
||||||
end: { line: 1, column: 15, offset: 14 },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
refFor: true,
|
refFor: true,
|
||||||
})
|
effect: false,
|
||||||
|
},
|
||||||
|
])
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
expect(code).contains('_setRef(n2, "foo", true)')
|
expect(code).contains('_setRef(n2, "foo", void 0, true)')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { genSetHtml } from './html'
|
||||||
import { genIf } from './if'
|
import { genIf } from './if'
|
||||||
import { genSetModelValue } from './modelValue'
|
import { genSetModelValue } from './modelValue'
|
||||||
import { genDynamicProps, genSetProp } from './prop'
|
import { genDynamicProps, genSetProp } from './prop'
|
||||||
import { genSetTemplateRef } from './templateRef'
|
import { genDeclareOldRef, genSetTemplateRef } from './templateRef'
|
||||||
import { genCreateTextNode, genSetText } from './text'
|
import { genCreateTextNode, genSetText } from './text'
|
||||||
import {
|
import {
|
||||||
type CodeFragment,
|
type CodeFragment,
|
||||||
|
@ -59,6 +59,8 @@ export function genOperation(
|
||||||
return genFor(oper, context)
|
return genFor(oper, context)
|
||||||
case IRNodeTypes.CREATE_COMPONENT_NODE:
|
case IRNodeTypes.CREATE_COMPONENT_NODE:
|
||||||
return genCreateComponent(oper, context)
|
return genCreateComponent(oper, context)
|
||||||
|
case IRNodeTypes.DECLARE_OLD_REF:
|
||||||
|
return genDeclareOldRef(oper)
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodegenContext } from '../generate'
|
||||||
import type { SetTemplateRefIRNode } from '../ir'
|
import type { DeclareOldRefIRNode, SetTemplateRefIRNode } from '../ir'
|
||||||
import { type CodeFragment, NEWLINE, genCall } from './utils'
|
import { type CodeFragment, NEWLINE, genCall } from './utils'
|
||||||
|
|
||||||
export function genSetTemplateRef(
|
export function genSetTemplateRef(
|
||||||
|
@ -10,11 +10,17 @@ export function genSetTemplateRef(
|
||||||
const { vaporHelper } = context
|
const { vaporHelper } = context
|
||||||
return [
|
return [
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
|
oper.effect && `r${oper.element} = `,
|
||||||
...genCall(
|
...genCall(
|
||||||
vaporHelper('setRef'),
|
vaporHelper('setRef'),
|
||||||
`n${oper.element}`,
|
`n${oper.element}`,
|
||||||
genExpression(oper.value, context),
|
genExpression(oper.value, context),
|
||||||
|
oper.effect ? `r${oper.element}` : oper.refFor ? 'void 0' : undefined,
|
||||||
oper.refFor && 'true',
|
oper.refFor && 'true',
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function genDeclareOldRef(oper: DeclareOldRefIRNode): CodeFragment[] {
|
||||||
|
return [NEWLINE, `let r${oper.id}`]
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ export enum IRNodeTypes {
|
||||||
CREATE_COMPONENT_NODE,
|
CREATE_COMPONENT_NODE,
|
||||||
|
|
||||||
WITH_DIRECTIVE,
|
WITH_DIRECTIVE,
|
||||||
|
DECLARE_OLD_REF, // consider make it more general
|
||||||
|
|
||||||
IF,
|
IF,
|
||||||
FOR,
|
FOR,
|
||||||
|
@ -158,6 +159,7 @@ export interface SetTemplateRefIRNode extends BaseIRNode {
|
||||||
element: number
|
element: number
|
||||||
value: SimpleExpressionNode
|
value: SimpleExpressionNode
|
||||||
refFor: boolean
|
refFor: boolean
|
||||||
|
effect: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetModelValueIRNode extends BaseIRNode {
|
export interface SetModelValueIRNode extends BaseIRNode {
|
||||||
|
@ -207,6 +209,11 @@ export interface CreateComponentIRNode extends BaseIRNode {
|
||||||
root: boolean
|
root: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DeclareOldRefIRNode extends BaseIRNode {
|
||||||
|
type: IRNodeTypes.DECLARE_OLD_REF
|
||||||
|
id: number
|
||||||
|
}
|
||||||
|
|
||||||
export type IRNode = OperationNode | RootIRNode
|
export type IRNode = OperationNode | RootIRNode
|
||||||
export type OperationNode =
|
export type OperationNode =
|
||||||
| SetPropIRNode
|
| SetPropIRNode
|
||||||
|
@ -224,6 +231,7 @@ export type OperationNode =
|
||||||
| IfIRNode
|
| IfIRNode
|
||||||
| ForIRNode
|
| ForIRNode
|
||||||
| CreateComponentIRNode
|
| CreateComponentIRNode
|
||||||
|
| DeclareOldRefIRNode
|
||||||
|
|
||||||
export enum DynamicFlag {
|
export enum DynamicFlag {
|
||||||
NONE = 0,
|
NONE = 0,
|
||||||
|
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import type { NodeTransform } from '../transform'
|
import type { NodeTransform } from '../transform'
|
||||||
import { IRNodeTypes } from '../ir'
|
import { IRNodeTypes } from '../ir'
|
||||||
import { normalizeBindShorthand } from './vBind'
|
import { normalizeBindShorthand } from './vBind'
|
||||||
import { findProp } from '../utils'
|
import { findProp, isConstantExpression } from '../utils'
|
||||||
import { EMPTY_EXPRESSION } from './utils'
|
import { EMPTY_EXPRESSION } from './utils'
|
||||||
|
|
||||||
export const transformTemplateRef: NodeTransform = (node, context) => {
|
export const transformTemplateRef: NodeTransform = (node, context) => {
|
||||||
|
@ -24,11 +24,20 @@ export const transformTemplateRef: NodeTransform = (node, context) => {
|
||||||
: EMPTY_EXPRESSION
|
: EMPTY_EXPRESSION
|
||||||
}
|
}
|
||||||
|
|
||||||
return () =>
|
return () => {
|
||||||
|
const id = context.reference()
|
||||||
|
const effect = !isConstantExpression(value)
|
||||||
|
effect &&
|
||||||
context.registerOperation({
|
context.registerOperation({
|
||||||
|
type: IRNodeTypes.DECLARE_OLD_REF,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
context.registerEffect([value], {
|
||||||
type: IRNodeTypes.SET_TEMPLATE_REF,
|
type: IRNodeTypes.SET_TEMPLATE_REF,
|
||||||
element: context.reference(),
|
element: id,
|
||||||
value,
|
value,
|
||||||
refFor: !!context.inVFor,
|
refFor: !!context.inVFor,
|
||||||
|
effect,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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'
|
import { makeRender } from '../_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
@ -23,4 +37,593 @@ describe('api: template ref', () => {
|
||||||
const { host } = render()
|
const { host } = render()
|
||||||
expect(el.value).toBe(host.children[0])
|
expect(el.value).toBe(host.children[0])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('string ref update', async () => {
|
||||||
|
const t0 = template('<div></div>')
|
||||||
|
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('<div></div>')
|
||||||
|
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('<div></div>')
|
||||||
|
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('<div></div>')
|
||||||
|
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('<div></div>')
|
||||||
|
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('<div></div>')
|
||||||
|
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('<div></div>')
|
||||||
|
const t1 = template('<div></div>')
|
||||||
|
const t2 = template('<div></div>')
|
||||||
|
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('<div id="foo"></div>')
|
||||||
|
const el = ref<HTMLElement>()
|
||||||
|
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(`<div id="foo"></div>`)
|
||||||
|
await nextTick()
|
||||||
|
// ref should be updated
|
||||||
|
expect(host.innerHTML).toBe(`<div id="foo">foo</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// #1834
|
||||||
|
test('exchange refs', async () => {
|
||||||
|
const refToggle = ref(false)
|
||||||
|
const spy = vi.fn()
|
||||||
|
|
||||||
|
const t0 = template('<p></p>')
|
||||||
|
const t1 = template('<i></i>')
|
||||||
|
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('<p></p>')
|
||||||
|
const t1 = template('<i></i>')
|
||||||
|
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('<ul></ul>')
|
||||||
|
const t1 = template('<li></li>')
|
||||||
|
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('<ul></ul>')
|
||||||
|
const t1 = template('<li></li>')
|
||||||
|
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('<div><div></div><ul></ul></div>')
|
||||||
|
const t1 = template('<li></li>')
|
||||||
|
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(
|
||||||
|
'<div><div>[object HTMLLIElement],[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>1</li><li>2</li><li>3</li><!--for--></ul></div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
list.value.splice(0, 1)
|
||||||
|
await nextTick()
|
||||||
|
expect(String(listRefs.value)).toBe(
|
||||||
|
'[object HTMLLIElement],[object HTMLLIElement]',
|
||||||
|
)
|
||||||
|
expect(host.innerHTML).toBe(
|
||||||
|
'<div><div>[object HTMLLIElement],[object HTMLLIElement]</div><ul><li>2</li><li>3</li><!--for--></ul></div>',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 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<keyof typeof refs>('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<any>(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)
|
||||||
|
// })
|
||||||
})
|
})
|
||||||
|
|
|
@ -27,7 +27,12 @@ export type RefEl = Element | ComponentInternalInstance
|
||||||
/**
|
/**
|
||||||
* Function for handling a template ref
|
* 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
|
if (!currentInstance) return
|
||||||
const { setupState, isUnmounted } = currentInstance
|
const { setupState, isUnmounted } = currentInstance
|
||||||
|
|
||||||
|
@ -42,6 +47,18 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) {
|
||||||
? (currentInstance.refs = {})
|
? (currentInstance.refs = {})
|
||||||
: 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)) {
|
if (isFunction(ref)) {
|
||||||
const invokeRefSetter = (value?: Element | Record<string, any>) => {
|
const invokeRefSetter = (value?: Element | Record<string, any>) => {
|
||||||
callWithErrorHandling(
|
callWithErrorHandling(
|
||||||
|
@ -117,4 +134,5 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) {
|
||||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ref
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue