Skip to content

Commit b0203a3

Browse files
committed
feat(expose): always expose $ instance properties on child refs
1 parent a5a66c5 commit b0203a3

File tree

5 files changed

+75
-29
lines changed

5 files changed

+75
-29
lines changed

packages/runtime-core/__tests__/apiExpose.spec.ts

+23-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ describe('api: expose', () => {
77
render() {},
88
setup(_, { expose }) {
99
expose({
10-
foo: ref(1),
10+
foo: 1,
1111
bar: ref(2)
1212
})
1313
return {
@@ -169,4 +169,26 @@ describe('api: expose', () => {
169169
const root = nodeOps.createElement('div')
170170
render(h(Parent), root)
171171
})
172+
173+
test('expose should allow access to built-in instance properties', () => {
174+
const Child = defineComponent({
175+
render() {
176+
return h('div')
177+
},
178+
setup(_, { expose }) {
179+
expose()
180+
return {}
181+
}
182+
})
183+
184+
const childRef = ref()
185+
const Parent = {
186+
setup() {
187+
return () => h(Child, { ref: childRef })
188+
}
189+
}
190+
const root = nodeOps.createElement('div')
191+
render(h(Parent), root)
192+
expect(childRef.value.$el.tag).toBe('div')
193+
})
172194
})

packages/runtime-core/src/component.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
createRenderContext,
1515
exposePropsOnRenderContext,
1616
exposeSetupStateOnRenderContext,
17-
ComponentPublicInstanceConstructor
17+
ComponentPublicInstanceConstructor,
18+
publicPropertiesMap
1819
} from './componentPublicInstance'
1920
import {
2021
ComponentPropsOptions,
@@ -169,7 +170,7 @@ export interface SetupContext<E = EmitsOptions> {
169170
attrs: Data
170171
slots: Slots
171172
emit: EmitFn<E>
172-
expose: (exposed: Record<string, any>) => void
173+
expose: (exposed?: Record<string, any>) => void
173174
}
174175

175176
/**
@@ -291,6 +292,7 @@ export interface ComponentInternalInstance {
291292

292293
// exposed properties via expose()
293294
exposed: Record<string, any> | null
295+
exposeProxy: Record<string, any> | null
294296

295297
/**
296298
* alternative proxy used only for runtime-compiled render functions using
@@ -447,6 +449,7 @@ export function createComponentInstance(
447449
render: null,
448450
proxy: null,
449451
exposed: null,
452+
exposeProxy: null,
450453
withProxy: null,
451454
effects: null,
452455
provides: parent ? parent.provides : Object.create(appContext.provides),
@@ -837,7 +840,7 @@ export function createSetupContext(
837840
if (__DEV__ && instance.exposed) {
838841
warn(`expose() should be called only once per setup().`)
839842
}
840-
instance.exposed = proxyRefs(exposed)
843+
instance.exposed = exposed || {}
841844
}
842845

843846
if (__DEV__) {
@@ -868,6 +871,23 @@ export function createSetupContext(
868871
}
869872
}
870873

874+
export function getExposeProxy(instance: ComponentInternalInstance) {
875+
if (instance.exposed) {
876+
return (
877+
instance.exposeProxy ||
878+
(instance.exposeProxy = new Proxy(proxyRefs(markRaw(instance.exposed)), {
879+
get(target, key: string) {
880+
if (key in target) {
881+
return target[key]
882+
} else if (key in publicPropertiesMap) {
883+
return publicPropertiesMap[key](instance)
884+
}
885+
}
886+
}))
887+
)
888+
}
889+
}
890+
871891
// record effects created during a component's setup() so that they can be
872892
// stopped when the component unmounts
873893
export function recordInstanceBoundEffect(

packages/runtime-core/src/componentOptions.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import {
1414
isString,
1515
isObject,
1616
isArray,
17-
EMPTY_OBJ,
1817
NOOP,
1918
isPromise
2019
} from '@vue/shared'
@@ -45,9 +44,7 @@ import {
4544
import {
4645
reactive,
4746
ComputedGetter,
48-
WritableComputedOptions,
49-
proxyRefs,
50-
toRef
47+
WritableComputedOptions
5148
} from '@vue/reactivity'
5249
import {
5350
ComponentObjectPropsOptions,
@@ -540,7 +537,7 @@ export let shouldCacheAccess = true
540537

541538
export function applyOptions(instance: ComponentInternalInstance) {
542539
const options = resolveMergedOptions(instance)
543-
const publicThis = instance.proxy!
540+
const publicThis = instance.proxy! as any
544541
const ctx = instance.ctx
545542

546543
// do not cache property access on public proxy during state initialization
@@ -773,12 +770,15 @@ export function applyOptions(instance: ComponentInternalInstance) {
773770

774771
if (isArray(expose)) {
775772
if (expose.length) {
776-
const exposed = instance.exposed || (instance.exposed = proxyRefs({}))
773+
const exposed = instance.exposed || (instance.exposed = {})
777774
expose.forEach(key => {
778-
exposed[key] = toRef(publicThis, key as any)
775+
Object.defineProperty(exposed, key, {
776+
get: () => publicThis[key],
777+
set: val => (publicThis[key] = val)
778+
})
779779
})
780780
} else if (!instance.exposed) {
781-
instance.exposed = EMPTY_OBJ
781+
instance.exposed = {}
782782
}
783783
}
784784

packages/runtime-core/src/componentPublicInstance.ts

+19-16
Original file line numberDiff line numberDiff line change
@@ -221,22 +221,25 @@ const getPublicInstance = (
221221
return getPublicInstance(i.parent)
222222
}
223223

224-
const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), {
225-
$: i => i,
226-
$el: i => i.vnode.el,
227-
$data: i => i.data,
228-
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
229-
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
230-
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
231-
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
232-
$parent: i => getPublicInstance(i.parent),
233-
$root: i => getPublicInstance(i.root),
234-
$emit: i => i.emit,
235-
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
236-
$forceUpdate: i => () => queueJob(i.update),
237-
$nextTick: i => nextTick.bind(i.proxy!),
238-
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
239-
} as PublicPropertiesMap)
224+
export const publicPropertiesMap: PublicPropertiesMap = extend(
225+
Object.create(null),
226+
{
227+
$: i => i,
228+
$el: i => i.vnode.el,
229+
$data: i => i.data,
230+
$props: i => (__DEV__ ? shallowReadonly(i.props) : i.props),
231+
$attrs: i => (__DEV__ ? shallowReadonly(i.attrs) : i.attrs),
232+
$slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots),
233+
$refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs),
234+
$parent: i => getPublicInstance(i.parent),
235+
$root: i => getPublicInstance(i.root),
236+
$emit: i => i.emit,
237+
$options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type),
238+
$forceUpdate: i => () => queueJob(i.update),
239+
$nextTick: i => nextTick.bind(i.proxy!),
240+
$watch: i => (__FEATURE_OPTIONS_API__ ? instanceWatch.bind(i) : NOOP)
241+
} as PublicPropertiesMap
242+
)
240243

241244
if (__COMPAT__) {
242245
installCompatInstanceProperties(publicPropertiesMap)

packages/runtime-core/src/renderer.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ComponentOptions,
2020
createComponentInstance,
2121
Data,
22+
getExposeProxy,
2223
setupComponent
2324
} from './component'
2425
import {
@@ -335,7 +336,7 @@ export const setRef = (
335336

336337
const refValue =
337338
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
338-
? vnode.component!.exposed || vnode.component!.proxy
339+
? getExposeProxy(vnode.component!) || vnode.component!.proxy
339340
: vnode.el
340341
const value = isUnmount ? null : refValue
341342

0 commit comments

Comments
 (0)