feat(expose): always expose $ instance properties on child refs
This commit is contained in:
parent
a5a66c5196
commit
b0203a3092
@ -7,7 +7,7 @@ describe('api: expose', () => {
|
|||||||
render() {},
|
render() {},
|
||||||
setup(_, { expose }) {
|
setup(_, { expose }) {
|
||||||
expose({
|
expose({
|
||||||
foo: ref(1),
|
foo: 1,
|
||||||
bar: ref(2)
|
bar: ref(2)
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
@ -169,4 +169,26 @@ describe('api: expose', () => {
|
|||||||
const root = nodeOps.createElement('div')
|
const root = nodeOps.createElement('div')
|
||||||
render(h(Parent), root)
|
render(h(Parent), root)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('expose should allow access to built-in instance properties', () => {
|
||||||
|
const Child = defineComponent({
|
||||||
|
render() {
|
||||||
|
return h('div')
|
||||||
|
},
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose()
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const childRef = ref()
|
||||||
|
const Parent = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Child, { ref: childRef })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(Parent), root)
|
||||||
|
expect(childRef.value.$el.tag).toBe('div')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -14,7 +14,8 @@ import {
|
|||||||
createRenderContext,
|
createRenderContext,
|
||||||
exposePropsOnRenderContext,
|
exposePropsOnRenderContext,
|
||||||
exposeSetupStateOnRenderContext,
|
exposeSetupStateOnRenderContext,
|
||||||
ComponentPublicInstanceConstructor
|
ComponentPublicInstanceConstructor,
|
||||||
|
publicPropertiesMap
|
||||||
} from './componentPublicInstance'
|
} from './componentPublicInstance'
|
||||||
import {
|
import {
|
||||||
ComponentPropsOptions,
|
ComponentPropsOptions,
|
||||||
@ -169,7 +170,7 @@ export interface SetupContext<E = EmitsOptions> {
|
|||||||
attrs: Data
|
attrs: Data
|
||||||
slots: Slots
|
slots: Slots
|
||||||
emit: EmitFn<E>
|
emit: EmitFn<E>
|
||||||
expose: (exposed: Record<string, any>) => void
|
expose: (exposed?: Record<string, any>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -291,6 +292,7 @@ export interface ComponentInternalInstance {
|
|||||||
|
|
||||||
// exposed properties via expose()
|
// exposed properties via expose()
|
||||||
exposed: Record<string, any> | null
|
exposed: Record<string, any> | null
|
||||||
|
exposeProxy: Record<string, any> | null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* alternative proxy used only for runtime-compiled render functions using
|
* alternative proxy used only for runtime-compiled render functions using
|
||||||
@ -447,6 +449,7 @@ export function createComponentInstance(
|
|||||||
render: null,
|
render: null,
|
||||||
proxy: null,
|
proxy: null,
|
||||||
exposed: null,
|
exposed: null,
|
||||||
|
exposeProxy: null,
|
||||||
withProxy: null,
|
withProxy: null,
|
||||||
effects: null,
|
effects: null,
|
||||||
provides: parent ? parent.provides : Object.create(appContext.provides),
|
provides: parent ? parent.provides : Object.create(appContext.provides),
|
||||||
@ -837,7 +840,7 @@ export function createSetupContext(
|
|||||||
if (__DEV__ && instance.exposed) {
|
if (__DEV__ && instance.exposed) {
|
||||||
warn(`expose() should be called only once per setup().`)
|
warn(`expose() should be called only once per setup().`)
|
||||||
}
|
}
|
||||||
instance.exposed = proxyRefs(exposed)
|
instance.exposed = exposed || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
@ -868,6 +871,23 @@ export function createSetupContext(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getExposeProxy(instance: ComponentInternalInstance) {
|
||||||
|
if (instance.exposed) {
|
||||||
|
return (
|
||||||
|
instance.exposeProxy ||
|
||||||
|
(instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
|
||||||
|
get(target, key: string) {
|
||||||
|
if (key in target) {
|
||||||
|
return target[key]
|
||||||
|
} else if (key in publicPropertiesMap) {
|
||||||
|
return publicPropertiesMap[key](instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// record effects created during a component's setup() so that they can be
|
// record effects created during a component's setup() so that they can be
|
||||||
// stopped when the component unmounts
|
// stopped when the component unmounts
|
||||||
export function recordInstanceBoundEffect(
|
export function recordInstanceBoundEffect(
|
||||||
|
@ -14,7 +14,6 @@ import {
|
|||||||
isString,
|
isString,
|
||||||
isObject,
|
isObject,
|
||||||
isArray,
|
isArray,
|
||||||
EMPTY_OBJ,
|
|
||||||
NOOP,
|
NOOP,
|
||||||
isPromise
|
isPromise
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
@ -45,9 +44,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
reactive,
|
reactive,
|
||||||
ComputedGetter,
|
ComputedGetter,
|
||||||
WritableComputedOptions,
|
WritableComputedOptions
|
||||||
proxyRefs,
|
|
||||||
toRef
|
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import {
|
import {
|
||||||
ComponentObjectPropsOptions,
|
ComponentObjectPropsOptions,
|
||||||
@ -540,7 +537,7 @@ export let shouldCacheAccess = true
|
|||||||
|
|
||||||
export function applyOptions(instance: ComponentInternalInstance) {
|
export function applyOptions(instance: ComponentInternalInstance) {
|
||||||
const options = resolveMergedOptions(instance)
|
const options = resolveMergedOptions(instance)
|
||||||
const publicThis = instance.proxy!
|
const publicThis = instance.proxy! as any
|
||||||
const ctx = instance.ctx
|
const ctx = instance.ctx
|
||||||
|
|
||||||
// do not cache property access on public proxy during state initialization
|
// do not cache property access on public proxy during state initialization
|
||||||
@ -773,12 +770,15 @@ export function applyOptions(instance: ComponentInternalInstance) {
|
|||||||
|
|
||||||
if (isArray(expose)) {
|
if (isArray(expose)) {
|
||||||
if (expose.length) {
|
if (expose.length) {
|
||||||
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
|
const exposed = instance.exposed || (instance.exposed = {})
|
||||||
expose.forEach(key => {
|
expose.forEach(key => {
|
||||||
exposed[key] = toRef(publicThis, key as any)
|
Object.defineProperty(exposed, key, {
|
||||||
|
get: () => publicThis[key],
|
||||||
|
set: val => (publicThis[key] = val)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
} else if (!instance.exposed) {
|
} else if (!instance.exposed) {
|
||||||
instance.exposed = EMPTY_OBJ
|
instance.exposed = {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,22 +221,25 @@ const getPublicInstance = (
|
|||||||
return getPublicInstance(i.parent)
|
return getPublicInstance(i.parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
|
export const publicPropertiesMap: PublicPropertiesMap = extend(
|
||||||
$: i => i,
|
Object.create(null),
|
||||||
$el: i => i.vnode.el,
|
{
|
||||||
$data: i => i.data,
|
$: i => i,
|
||||||
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
|
$el: i => i.vnode.el,
|
||||||
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
|
$data: i => i.data,
|
||||||
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
|
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
|
||||||
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
|
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
|
||||||
$parent: i => getPublicInstance(i.parent),
|
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
|
||||||
$root: i => getPublicInstance(i.root),
|
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
|
||||||
$emit: i => i.emit,
|
$parent: i => getPublicInstance(i.parent),
|
||||||
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
|
$root: i => getPublicInstance(i.root),
|
||||||
$forceUpdate: i => () => queueJob(i.update),
|
$emit: i => i.emit,
|
||||||
$nextTick: i => nextTick.bind(i.proxy!),
|
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
|
||||||
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
|
$forceUpdate: i => () => queueJob(i.update),
|
||||||
} as PublicPropertiesMap)
|
$nextTick: i => nextTick.bind(i.proxy!),
|
||||||
|
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
|
||||||
|
} as PublicPropertiesMap
|
||||||
|
)
|
||||||
|
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
installCompatInstanceProperties(publicPropertiesMap)
|
installCompatInstanceProperties(publicPropertiesMap)
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
ComponentOptions,
|
ComponentOptions,
|
||||||
createComponentInstance,
|
createComponentInstance,
|
||||||
Data,
|
Data,
|
||||||
|
getExposeProxy,
|
||||||
setupComponent
|
setupComponent
|
||||||
} from './component'
|
} from './component'
|
||||||
import {
|
import {
|
||||||
@ -335,7 +336,7 @@ export const setRef = (
|
|||||||
|
|
||||||
const refValue =
|
const refValue =
|
||||||
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
|
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
|
||||||
? vnode.component!.exposed || vnode.component!.proxy
|
? getExposeProxy(vnode.component!) || vnode.component!.proxy
|
||||||
: vnode.el
|
: vnode.el
|
||||||
const value = isUnmount ? null : refValue
|
const value = isUnmount ? null : refValue
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user