From 43521376a0401ec42d4fab710b46b3c194dfbef9 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Sat, 20 Apr 2024 23:17:59 +0800 Subject: [PATCH 1/5] feat(runtime-vapor): impl ref on component --- packages/runtime-vapor/src/component.ts | 6 ++++ packages/runtime-vapor/src/dom/templateRef.ts | 31 ++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 43e89aaa5..64c0a4413 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -327,6 +327,12 @@ export function createComponentInstance( return instance } +export const isVaporComponent = ( + val: any, +): val is ComponentInternalInstance => { + return val && componentKey in val +} + function getAttrsProxy(instance: ComponentInternalInstance): Data { return ( instance.attrsProxy || diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts index 2faeee53e..361a5ffe1 100644 --- a/packages/runtime-vapor/src/dom/templateRef.ts +++ b/packages/runtime-vapor/src/dom/templateRef.ts @@ -4,7 +4,11 @@ import { isRef, onScopeDispose, } from '@vue/reactivity' -import { currentInstance } from '../component' +import { + type ComponentInternalInstance, + currentInstance, + isVaporComponent, +} from '../component' import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling' import { EMPTY_OBJ, @@ -18,11 +22,12 @@ import { warn } from '../warning' import { queuePostFlushCb } from '../scheduler' export type NodeRef = string | Ref | ((ref: Element) => void) +export type RefEl = Element | ComponentInternalInstance /** * Function for handling a template ref */ -export function setRef(el: Element, ref: NodeRef, refFor = false) { +export function setRef(el: RefEl, ref: NodeRef, refFor = false) { if (!currentInstance) return const { setupState, isUnmounted } = currentInstance @@ -30,13 +35,17 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) { return } + const refValue = isVaporComponent(el) ? el.exposed || currentInstance : el + const refs = currentInstance.refs === EMPTY_OBJ ? (currentInstance.refs = {}) : currentInstance.refs if (isFunction(ref)) { - const invokeRefSetter = (value: Element | null) => { + const invokeRefSetter = ( + value: Element | ComponentInternalInstance['exposed'] | null, + ) => { callWithErrorHandling( ref, currentInstance, @@ -45,7 +54,7 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) { ) } - invokeRefSetter(el) + invokeRefSetter(refValue) onScopeDispose(() => invokeRefSetter(null)) } else { const _isString = isString(ref) @@ -62,7 +71,7 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) { : ref.value if (!isArray(existing)) { - existing = [el] + existing = [refValue] if (_isString) { refs[ref] = existing if (hasOwn(setupState, ref)) { @@ -75,16 +84,16 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) { } else { ref.value = existing } - } else if (!existing.includes(el)) { - existing.push(el) + } else if (!existing.includes(refValue)) { + existing.push(refValue) } } else if (_isString) { - refs[ref] = el + refs[ref] = refValue if (hasOwn(setupState, ref)) { - setupState[ref] = el + setupState[ref] = refValue } } else if (_isRef) { - ref.value = el + ref.value = refValue } else if (__DEV__) { warn('Invalid template ref type:', ref, `(${typeof ref})`) } @@ -95,7 +104,7 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) { onScopeDispose(() => { queuePostFlushCb(() => { if (isArray(existing)) { - remove(existing, el) + remove(existing, refValue) } else if (_isString) { refs[ref] = null if (hasOwn(setupState, ref)) { From a2d0d4d65fcdedb80699785d11f11da9e127f73e Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Sat, 20 Apr 2024 23:49:36 +0800 Subject: [PATCH 2/5] test(runtime-vapor): add template ref test case --- .../__tests__/dom/templateRef.spec.ts | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 packages/runtime-vapor/__tests__/dom/templateRef.spec.ts diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts new file mode 100644 index 000000000..280d5ad78 --- /dev/null +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -0,0 +1,54 @@ +import { createComponent, ref, setRef, template } from '../../src' +import { makeRender } from '../_utils' + +const define = makeRender() + +describe('api: template ref', () => { + test('ref on element', () => { + const t0 = template('
hello
') + const { render } = define({ + setup() { + return { + foo: ref(null), + } + }, + render() { + const n0 = t0() + setRef(n0 as any, 'foo') + return n0 + }, + }) + + const { host, instance } = render() + expect(instance.refs.foo).toBe(host.firstChild) + }) + + test('ref on component', () => { + const exposeRef = ref | undefined>() + const { component: Child } = define({ + setup(_, { expose }) { + expose(exposeRef.value) + }, + }) + const compRef = ref() + const { render } = define({ + setup() { + return { + compRef, + } + }, + render: () => { + const n0 = createComponent(Child) + setRef(n0, 'compRef') + return n0 + }, + }) + + expect(render().instance.refs.compRef).toBeDefined() + + const exposeValue = { foo: 'bar' } + exposeRef.value = exposeValue + + expect(render().instance.refs.compRef).toEqual(exposeValue) + }) +}) From 4baeb264e199eb8680e7711ab27d0286988609d9 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Wed, 24 Apr 2024 11:27:54 +0800 Subject: [PATCH 3/5] test(runtimer-vapor): update test case --- .../runtime-vapor/__tests__/apiExpose.spec.ts | 36 +++++++++++++++ .../__tests__/dom/templateRef.spec.ts | 44 ++++--------------- packages/runtime-vapor/src/component.ts | 10 ++--- packages/runtime-vapor/src/dom/templateRef.ts | 4 +- 4 files changed, 51 insertions(+), 43 deletions(-) create mode 100644 packages/runtime-vapor/__tests__/apiExpose.spec.ts diff --git a/packages/runtime-vapor/__tests__/apiExpose.spec.ts b/packages/runtime-vapor/__tests__/apiExpose.spec.ts new file mode 100644 index 000000000..694a89d5d --- /dev/null +++ b/packages/runtime-vapor/__tests__/apiExpose.spec.ts @@ -0,0 +1,36 @@ +import { ref } from '@vue/reactivity' +import { createComponent } from '../src/apiCreateComponent' +import { setRef } from '../src/dom/templateRef' +import { makeRender } from './_utils' + +const define = makeRender() +describe('api: expose', () => { + test('string ref mount (component)', () => { + const { component: Child } = define({ + setup(_, { expose }) { + expose({ + foo: 1, + bar: ref(2), + }) + return { + bar: ref(3), + baz: ref(4), + } + }, + }) + const childRef = ref() + const { render } = define({ + render: () => { + const n0 = createComponent(Child) + setRef(n0, childRef) + return n0 + }, + }) + + render() + expect(childRef.value).toBeTruthy() + expect(childRef.value.foo).toBe(1) + expect(childRef.value.bar).toBe(2) + expect(childRef.value.baz).toBeUndefined() + }) +}) diff --git a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts index 280d5ad78..6dc10eff3 100644 --- a/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts +++ b/packages/runtime-vapor/__tests__/dom/templateRef.spec.ts @@ -1,54 +1,26 @@ -import { createComponent, ref, setRef, template } from '../../src' +import { ref, setRef, template } from '../../src' import { makeRender } from '../_utils' const define = makeRender() describe('api: template ref', () => { - test('ref on element', () => { - const t0 = template('
hello
') + test('string ref mount', () => { + const t0 = template('
') + const el = ref(null) const { render } = define({ setup() { return { - foo: ref(null), + refKey: el, } }, render() { const n0 = t0() - setRef(n0 as any, 'foo') + setRef(n0 as Element, 'refKey') return n0 }, }) - const { host, instance } = render() - expect(instance.refs.foo).toBe(host.firstChild) - }) - - test('ref on component', () => { - const exposeRef = ref | undefined>() - const { component: Child } = define({ - setup(_, { expose }) { - expose(exposeRef.value) - }, - }) - const compRef = ref() - const { render } = define({ - setup() { - return { - compRef, - } - }, - render: () => { - const n0 = createComponent(Child) - setRef(n0, 'compRef') - return n0 - }, - }) - - expect(render().instance.refs.compRef).toBeDefined() - - const exposeValue = { foo: 'bar' } - exposeRef.value = exposeValue - - expect(render().instance.refs.compRef).toEqual(exposeValue) + const { host } = render() + expect(el.value).toBe(host.children[0]) }) }) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 64c0a4413..4725a14b6 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -1,5 +1,5 @@ import { EffectScope, isRef } from '@vue/reactivity' -import { EMPTY_OBJ, isArray, isFunction } from '@vue/shared' +import { EMPTY_OBJ, hasOwn, isArray, isFunction } from '@vue/shared' import type { Block } from './apiRender' import type { DirectiveBinding } from './directives' import { @@ -327,10 +327,10 @@ export function createComponentInstance( return instance } -export const isVaporComponent = ( - val: any, -): val is ComponentInternalInstance => { - return val && componentKey in val +export function isVaporComponent( + val: unknown, +): val is ComponentInternalInstance { + return !!val && hasOwn(val, componentKey) } function getAttrsProxy(instance: ComponentInternalInstance): Data { diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts index 361a5ffe1..260fa5a6c 100644 --- a/packages/runtime-vapor/src/dom/templateRef.ts +++ b/packages/runtime-vapor/src/dom/templateRef.ts @@ -44,7 +44,7 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { if (isFunction(ref)) { const invokeRefSetter = ( - value: Element | ComponentInternalInstance['exposed'] | null, + value: Element | Record | undefined, ) => { callWithErrorHandling( ref, @@ -55,7 +55,7 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { } invokeRefSetter(refValue) - onScopeDispose(() => invokeRefSetter(null)) + onScopeDispose(() => invokeRefSetter(undefined)) } else { const _isString = isString(ref) const _isRef = isRef(ref) From 3ca2f2100ddefe6b126923199979fdb75daae189 Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Wed, 24 Apr 2024 11:43:12 +0800 Subject: [PATCH 4/5] feat(runtime-vapor): simplify code --- packages/runtime-vapor/src/dom/templateRef.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts index 260fa5a6c..0496b6a40 100644 --- a/packages/runtime-vapor/src/dom/templateRef.ts +++ b/packages/runtime-vapor/src/dom/templateRef.ts @@ -43,9 +43,7 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { : currentInstance.refs if (isFunction(ref)) { - const invokeRefSetter = ( - value: Element | Record | undefined, - ) => { + const invokeRefSetter = (value?: Element | Record) => { callWithErrorHandling( ref, currentInstance, @@ -55,7 +53,7 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { } invokeRefSetter(refValue) - onScopeDispose(() => invokeRefSetter(undefined)) + onScopeDispose(() => invokeRefSetter()) } else { const _isString = isString(ref) const _isRef = isRef(ref) From b3ad1c969b1d914c6ceba7f1254504e28959c9ce Mon Sep 17 00:00:00 2001 From: Doctorwu Date: Wed, 24 Apr 2024 22:20:07 +0800 Subject: [PATCH 5/5] fix(runtime-vapor): tweak expose fallback value --- .../runtime-vapor/__tests__/apiExpose.spec.ts | 65 ++++++++++++++++++- packages/runtime-vapor/src/dom/templateRef.ts | 2 +- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/packages/runtime-vapor/__tests__/apiExpose.spec.ts b/packages/runtime-vapor/__tests__/apiExpose.spec.ts index 694a89d5d..4323991d0 100644 --- a/packages/runtime-vapor/__tests__/apiExpose.spec.ts +++ b/packages/runtime-vapor/__tests__/apiExpose.spec.ts @@ -1,11 +1,15 @@ -import { ref } from '@vue/reactivity' +import { ref, shallowRef } from '@vue/reactivity' import { createComponent } from '../src/apiCreateComponent' import { setRef } from '../src/dom/templateRef' import { makeRender } from './_utils' +import { + type ComponentInternalInstance, + getCurrentInstance, +} from '../src/component' const define = makeRender() describe('api: expose', () => { - test('string ref mount (component)', () => { + test('via setup context', () => { const { component: Child } = define({ setup(_, { expose }) { expose({ @@ -33,4 +37,61 @@ describe('api: expose', () => { expect(childRef.value.bar).toBe(2) expect(childRef.value.baz).toBeUndefined() }) + + test('via setup context (expose empty)', () => { + let childInstance: ComponentInternalInstance | null = null + const { component: Child } = define({ + setup(_) { + childInstance = getCurrentInstance() + }, + }) + const childRef = shallowRef() + const { render } = define({ + render: () => { + const n0 = createComponent(Child) + setRef(n0, childRef) + return n0 + }, + }) + + render() + expect(childInstance!.exposed).toBeUndefined() + expect(childRef.value).toBe(childInstance!) + }) + + test('warning for ref', () => { + const { render } = define({ + setup(_, { expose }) { + expose(ref(1)) + }, + }) + render() + expect( + 'expose() should be passed a plain object, received ref', + ).toHaveBeenWarned() + }) + + test('warning for array', () => { + const { render } = define({ + setup(_, { expose }) { + expose(['focus']) + }, + }) + render() + expect( + 'expose() should be passed a plain object, received array', + ).toHaveBeenWarned() + }) + + test('warning for function', () => { + const { render } = define({ + setup(_, { expose }) { + expose(() => null) + }, + }) + render() + expect( + 'expose() should be passed a plain object, received function', + ).toHaveBeenWarned() + }) }) diff --git a/packages/runtime-vapor/src/dom/templateRef.ts b/packages/runtime-vapor/src/dom/templateRef.ts index 0496b6a40..25a9b014b 100644 --- a/packages/runtime-vapor/src/dom/templateRef.ts +++ b/packages/runtime-vapor/src/dom/templateRef.ts @@ -35,7 +35,7 @@ export function setRef(el: RefEl, ref: NodeRef, refFor = false) { return } - const refValue = isVaporComponent(el) ? el.exposed || currentInstance : el + const refValue = isVaporComponent(el) ? el.exposed || el : el const refs = currentInstance.refs === EMPTY_OBJ