diff --git a/packages/runtime-core/__tests__/apiExpose.spec.ts b/packages/runtime-core/__tests__/apiExpose.spec.ts index 7695360be59..a74edfad543 100644 --- a/packages/runtime-core/__tests__/apiExpose.spec.ts +++ b/packages/runtime-core/__tests__/apiExpose.spec.ts @@ -141,4 +141,32 @@ describe('api: expose', () => { expect(childRef.value).toBeTruthy() expect(childRef.value.foo).toBe(1) }) + + test('with $parent/$root', () => { + const Child = defineComponent({ + render() { + expect((this.$parent! as any).foo).toBe(1) + expect((this.$parent! as any).bar).toBe(undefined) + expect((this.$root! as any).foo).toBe(1) + expect((this.$root! as any).bar).toBe(undefined) + } + }) + + const Parent = defineComponent({ + expose: [], + setup(_, { expose }) { + expose({ + foo: 1 + }) + return { + bar: 2 + } + }, + render() { + return h(Child) + } + }) + const root = nodeOps.createElement('div') + render(h(Parent), root) + }) }) diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 494ae41dc47..406e8e0e4f6 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -510,6 +510,10 @@ export function validateComponentName(name: string, config: AppConfig) { } } +export function isStatefulComponent(instance: ComponentInternalInstance) { + return instance.vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT +} + export let isInSSRComponentSetup = false export function setupComponent( @@ -518,8 +522,8 @@ export function setupComponent( ) { isInSSRComponentSetup = isSSR - const { props, children, shapeFlag } = instance.vnode - const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT + const { props, children } = instance.vnode + const isStateful = isStatefulComponent(instance) initProps(instance, props, isStateful, isSSR) initSlots(instance, children) diff --git a/packages/runtime-core/src/componentPublicInstance.ts b/packages/runtime-core/src/componentPublicInstance.ts index ba7eda893b1..c493fea0b90 100644 --- a/packages/runtime-core/src/componentPublicInstance.ts +++ b/packages/runtime-core/src/componentPublicInstance.ts @@ -1,4 +1,8 @@ -import { ComponentInternalInstance, Data } from './component' +import { + ComponentInternalInstance, + Data, + isStatefulComponent +} from './component' import { nextTick, queueJob } from './scheduler' import { instanceWatch, WatchOptions, WatchStopHandle } from './apiWatch' import { @@ -207,8 +211,11 @@ type PublicPropertiesMap = Record any> */ const getPublicInstance = ( i: ComponentInternalInstance | null -): ComponentPublicInstance | null => - i && (i.proxy ? i.proxy : getPublicInstance(i.parent)) +): ComponentPublicInstance | ComponentInternalInstance['exposed'] | null => { + if (!i) return null + if (isStatefulComponent(i)) return i.exposed ? i.exposed : i.proxy + return getPublicInstance(i.parent) +} const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), { $: i => i, @@ -219,7 +226,7 @@ const publicPropertiesMap: PublicPropertiesMap = extend(Object.create(null), { $slots: i => (__DEV__ ? shallowReadonly(i.slots) : i.slots), $refs: i => (__DEV__ ? shallowReadonly(i.refs) : i.refs), $parent: i => getPublicInstance(i.parent), - $root: i => i.root && i.root.proxy, + $root: i => getPublicInstance(i.root), $emit: i => i.emit, $options: i => (__FEATURE_OPTIONS_API__ ? resolveMergedOptions(i) : i.type), $forceUpdate: i => () => queueJob(i.update),