mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): template ref on component (#185)
This commit is contained in:
parent
7fe4712831
commit
b7b652eb71
|
@ -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()
|
||||||
|
})
|
||||||
|
})
|
|
@ -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])
|
||||||
|
})
|
||||||
|
})
|
|
@ -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 ||
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
Loading…
Reference in New Issue