render() {},
setup(_, { expose }) {
expose({
- foo: ref(1),
+ foo: 1,
bar: ref(2)
})
return {
const root = nodeOps.createElement('div')
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')
+ })
})
createRenderContext,
exposePropsOnRenderContext,
exposeSetupStateOnRenderContext,
- ComponentPublicInstanceConstructor
+ ComponentPublicInstanceConstructor,
+ publicPropertiesMap
} from './componentPublicInstance'
import {
ComponentPropsOptions,
attrs: Data
slots: Slots
emit: EmitFn<E>
- expose: (exposed: Record<string, any>) => void
+ expose: (exposed?: Record<string, any>) => void
}
/**
// exposed properties via expose()
exposed: Record<string, any> | null
+ exposeProxy: Record<string, any> | null
/**
* alternative proxy used only for runtime-compiled render functions using
render: null,
proxy: null,
exposed: null,
+ exposeProxy: null,
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
if (__DEV__ && instance.exposed) {
warn(`expose() should be called only once per setup().`)
}
- instance.exposed = proxyRefs(exposed)
+ instance.exposed = exposed || {}
}
if (__DEV__) {
}
}
+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
// stopped when the component unmounts
export function recordInstanceBoundEffect(
isString,
isObject,
isArray,
- EMPTY_OBJ,
NOOP,
isPromise
} from '@vue/shared'
import {
reactive,
ComputedGetter,
- WritableComputedOptions,
- proxyRefs,
- toRef
+ WritableComputedOptions
} from '@vue/reactivity'
import {
ComponentObjectPropsOptions,
export function applyOptions(instance: ComponentInternalInstance) {
const options = resolveMergedOptions(instance)
- const publicThis = instance.proxy!
+ const publicThis = instance.proxy! as any
const ctx = instance.ctx
// do not cache property access on public proxy during state initialization
if (isArray(expose)) {
if (expose.length) {
- const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
+ const exposed = instance.exposed || (instance.exposed = {})
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) {
- instance.exposed = EMPTY_OBJ
+ instance.exposed = {}
}
}
return getPublicInstance(i.parent)
}
-const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
- $: i => i,
- $el: i => i.vnode.el,
- $data: i => i.data,
- $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
- $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
- $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
- $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
- $parent: i => getPublicInstance(i.parent),
- $root: i => getPublicInstance(i.root),
- $emit: i => i.emit,
- $options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
- $forceUpdate: i => () => queueJob(i.update),
- $nextTick: i => nextTick.bind(i.proxy!),
- $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
-} as PublicPropertiesMap)
+export const publicPropertiesMap: PublicPropertiesMap = extend(
+ Object.create(null),
+ {
+ $: i => i,
+ $el: i => i.vnode.el,
+ $data: i => i.data,
+ $props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
+ $attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
+ $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
+ $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
+ $parent: i => getPublicInstance(i.parent),
+ $root: i => getPublicInstance(i.root),
+ $emit: i => i.emit,
+ $options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
+ $forceUpdate: i => () => queueJob(i.update),
+ $nextTick: i => nextTick.bind(i.proxy!),
+ $watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
+ } as PublicPropertiesMap
+)
if (__COMPAT__) {
installCompatInstanceProperties(publicPropertiesMap)
ComponentOptions,
createComponentInstance,
Data,
+ getExposeProxy,
setupComponent
} from './component'
import {
const refValue =
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
- ? vnode.component!.exposed || vnode.component!.proxy
+ ? getExposeProxy(vnode.component!) || vnode.component!.proxy
: vnode.el
const value = isUnmount ? null : refValue