feat(runtime-vapor): template ref on component (#185)

This commit is contained in:
Doctor Wu 2024-04-25 04:57:45 +08:00 committed by GitHub
parent 7fe4712831
commit b7b652eb71
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 149 additions and 13 deletions

View File

@ -0,0 +1,97 @@
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('via setup context', () => {
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()
})
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()
})
})

View File

@ -0,0 +1,26 @@
import { ref, setRef, template } from '../../src'
import { makeRender } from '../_utils'
const define = makeRender()
describe('api: template ref', () => {
test('string ref mount', () => {
const t0 = template('<div ref="refKey"></div>')
const el = ref(null)
const { render } = define({
setup() {
return {
refKey: el,
}
},
render() {
const n0 = t0()
setRef(n0 as Element, 'refKey')
return n0
},
})
const { host } = render()
expect(el.value).toBe(host.children[0])
})
})

View File

@ -1,5 +1,5 @@
import { EffectScope, isRef } from '@vue/reactivity' 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 { Block } from './apiRender'
import type { DirectiveBinding } from './directives' import type { DirectiveBinding } from './directives'
import { import {
@ -327,6 +327,12 @@ export function createComponentInstance(
return instance return instance
} }
export function isVaporComponent(
val: unknown,
): val is ComponentInternalInstance {
return !!val && hasOwn(val, componentKey)
}
function getAttrsProxy(instance: ComponentInternalInstance): Data { function getAttrsProxy(instance: ComponentInternalInstance): Data {
return ( return (
instance.attrsProxy || instance.attrsProxy ||

View File

@ -4,7 +4,11 @@ import {
isRef, isRef,
onScopeDispose, onScopeDispose,
} from '@vue/reactivity' } from '@vue/reactivity'
import { currentInstance } from '../component' import {
type ComponentInternalInstance,
currentInstance,
isVaporComponent,
} from '../component'
import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling' import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
import { import {
EMPTY_OBJ, EMPTY_OBJ,
@ -18,11 +22,12 @@ import { warn } from '../warning'
import { queuePostFlushCb } from '../scheduler' import { queuePostFlushCb } from '../scheduler'
export type NodeRef = string | Ref | ((ref: Element) => void) export type NodeRef = string | Ref | ((ref: Element) => void)
export type RefEl = Element | ComponentInternalInstance
/** /**
* Function for handling a template ref * 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 if (!currentInstance) return
const { setupState, isUnmounted } = currentInstance const { setupState, isUnmounted } = currentInstance
@ -30,13 +35,15 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
return return
} }
const refValue = isVaporComponent(el) ? el.exposed || el : el
const refs = const refs =
currentInstance.refs === EMPTY_OBJ currentInstance.refs === EMPTY_OBJ
? (currentInstance.refs = {}) ? (currentInstance.refs = {})
: currentInstance.refs : currentInstance.refs
if (isFunction(ref)) { if (isFunction(ref)) {
const invokeRefSetter = (value: Element | null) => { const invokeRefSetter = (value?: Element | Record<string, any>) => {
callWithErrorHandling( callWithErrorHandling(
ref, ref,
currentInstance, currentInstance,
@ -45,8 +52,8 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
) )
} }
invokeRefSetter(el) invokeRefSetter(refValue)
onScopeDispose(() => invokeRefSetter(null)) onScopeDispose(() => invokeRefSetter())
} else { } else {
const _isString = isString(ref) const _isString = isString(ref)
const _isRef = isRef(ref) const _isRef = isRef(ref)
@ -62,7 +69,7 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
: ref.value : ref.value
if (!isArray(existing)) { if (!isArray(existing)) {
existing = [el] existing = [refValue]
if (_isString) { if (_isString) {
refs[ref] = existing refs[ref] = existing
if (hasOwn(setupState, ref)) { if (hasOwn(setupState, ref)) {
@ -75,16 +82,16 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
} else { } else {
ref.value = existing ref.value = existing
} }
} else if (!existing.includes(el)) { } else if (!existing.includes(refValue)) {
existing.push(el) existing.push(refValue)
} }
} else if (_isString) { } else if (_isString) {
refs[ref] = el refs[ref] = refValue
if (hasOwn(setupState, ref)) { if (hasOwn(setupState, ref)) {
setupState[ref] = el setupState[ref] = refValue
} }
} else if (_isRef) { } else if (_isRef) {
ref.value = el ref.value = refValue
} else if (__DEV__) { } else if (__DEV__) {
warn('Invalid template ref type:', ref, `(${typeof ref})`) warn('Invalid template ref type:', ref, `(${typeof ref})`)
} }
@ -95,7 +102,7 @@ export function setRef(el: Element, ref: NodeRef, refFor = false) {
onScopeDispose(() => { onScopeDispose(() => {
queuePostFlushCb(() => { queuePostFlushCb(() => {
if (isArray(existing)) { if (isArray(existing)) {
remove(existing, el) remove(existing, refValue)
} else if (_isString) { } else if (_isString) {
refs[ref] = null refs[ref] = null
if (hasOwn(setupState, ref)) { if (hasOwn(setupState, ref)) {