Skip to content

Commit

Permalink
feat(expose): always expose $ instance properties on child refs
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 25, 2021
1 parent a5a66c5 commit b0203a3
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 29 deletions.
24 changes: 23 additions & 1 deletion packages/runtime-core/__tests__/apiExpose.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe('api: expose', () => {
render() {},
setup(_, { expose }) {
expose({
foo: ref(1),
foo: 1,
bar: ref(2)
})
return {
Expand Down Expand Up @@ -169,4 +169,26 @@ describe('api: expose', () => {
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')
})
})
26 changes: 23 additions & 3 deletions packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
createRenderContext,
exposePropsOnRenderContext,
exposeSetupStateOnRenderContext,
ComponentPublicInstanceConstructor
ComponentPublicInstanceConstructor,
publicPropertiesMap
} from './componentPublicInstance'
import {
ComponentPropsOptions,
Expand Down Expand Up @@ -169,7 +170,7 @@ export interface SetupContext<E = EmitsOptions> {
attrs: Data
slots: Slots
emit: EmitFn<E>
expose: (exposed: Record<string, any>) => void
expose: (exposed?: Record<string, any>) => void
}

/**
Expand Down Expand Up @@ -291,6 +292,7 @@ export interface ComponentInternalInstance {

// exposed properties via expose()
exposed: Record<string, any> | null
exposeProxy: Record<string, any> | null

/**
* alternative proxy used only for runtime-compiled render functions using
Expand Down Expand Up @@ -447,6 +449,7 @@ export function createComponentInstance(
render: null,
proxy: null,
exposed: null,
exposeProxy: null,
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
Expand Down Expand Up @@ -837,7 +840,7 @@ export function createSetupContext(
if (__DEV__ && instance.exposed) {
warn(`expose() should be called only once per setup().`)
}
instance.exposed = proxyRefs(exposed)
instance.exposed = exposed || {}
}

if (__DEV__) {
Expand Down Expand Up @@ -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
// stopped when the component unmounts
export function recordInstanceBoundEffect(
Expand Down
16 changes: 8 additions & 8 deletions packages/runtime-core/src/componentOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {
isString,
isObject,
isArray,
EMPTY_OBJ,
NOOP,
isPromise
} from '@vue/shared'
Expand Down Expand Up @@ -45,9 +44,7 @@ import {
import {
reactive,
ComputedGetter,
WritableComputedOptions,
proxyRefs,
toRef
WritableComputedOptions
} from '@vue/reactivity'
import {
ComponentObjectPropsOptions,
Expand Down Expand Up @@ -540,7 +537,7 @@ export let shouldCacheAccess = true

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
Expand Down Expand Up @@ -773,12 +770,15 @@ export function applyOptions(instance: ComponentInternalInstance) {

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 = {}
}
}

Expand Down
35 changes: 19 additions & 16 deletions packages/runtime-core/src/componentPublicInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,22 +221,25 @@ const getPublicInstance = (
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)
Expand Down
3 changes: 2 additions & 1 deletion packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ComponentOptions,
createComponentInstance,
Data,
getExposeProxy,
setupComponent
} from './component'
import {
Expand Down Expand Up @@ -335,7 +336,7 @@ export const setRef = (

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

Expand Down

0 comments on commit b0203a3

Please sign in to comment.