Skip to content

Commit c6033fb

Browse files
committed
feat(runtime-core): add expose() API
WIP - ref: vuejs/rfcs#210
1 parent c7b4a37 commit c6033fb

File tree

5 files changed

+67
-2
lines changed

5 files changed

+67
-2
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { nodeOps, render } from '@vue/runtime-test'
2+
import { h, expose, ref } from '../src'
3+
4+
describe('api: expose', () => {
5+
test('component ref should only expose exposed properties', () => {
6+
const Child = {
7+
render() {},
8+
setup() {
9+
expose({
10+
foo: ref(1),
11+
bar: ref(2)
12+
})
13+
return {
14+
bar: ref(3),
15+
baz: ref(4)
16+
}
17+
}
18+
}
19+
const childRef = ref()
20+
const Parent = {
21+
setup() {
22+
return () => h(Child, { ref: childRef })
23+
}
24+
}
25+
const root = nodeOps.createElement('div')
26+
render(h(Parent), root)
27+
expect(childRef.value).toBeTruthy()
28+
expect(childRef.value.foo).toBe(1)
29+
expect(childRef.value.bar).toBe(2)
30+
expect(childRef.value.baz).toBeUndefined()
31+
})
32+
33+
test('expose called outside of setup()', () => {
34+
expose({})
35+
expect(
36+
`expose() is called when there is no active component instance`
37+
).toHaveBeenWarned()
38+
})
39+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { proxyRefs } from '@vue/reactivity'
2+
import { getCurrentInstance } from './component'
3+
import { warn } from './warning'
4+
5+
// TODO: refine and merge RFC (https://github.com/vuejs/rfcs/pull/210)
6+
// TODO: corresponding option (https://github.com/vuejs/rfcs/pull/135)
7+
8+
export function expose(values: Record<string, any>) {
9+
const instance = getCurrentInstance()
10+
if (instance) {
11+
Object.assign(
12+
instance.exposed || (instance.exposed = proxyRefs({})),
13+
values
14+
)
15+
} else if (__DEV__) {
16+
warn(
17+
`expose() is called when there is no active component instance to be ` +
18+
`associated with.`
19+
)
20+
}
21+
}

packages/runtime-core/src/component.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,9 @@ export interface ComponentInternalInstance {
270270
// main proxy that serves as the public instance (`this`)
271271
proxy: ComponentPublicInstance | null
272272

273+
// exposed properties via expose()
274+
exposed: Record<string, any> | null
275+
273276
/**
274277
* alternative proxy used only for runtime-compiled render functions using
275278
* `with` block
@@ -416,6 +419,7 @@ export function createComponentInstance(
416419
render: null,
417420
proxy: null,
418421
withProxy: null,
422+
exposed: null,
419423
effects: null,
420424
provides: parent ? parent.provides : Object.create(appContext.provides),
421425
accessCache: null!,

packages/runtime-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export {
4040
onErrorCaptured
4141
} from './apiLifecycle'
4242
export { provide, inject } from './apiInject'
43+
export { expose } from './apiExpose'
4344
export { nextTick } from './scheduler'
4445
export { defineComponent } from './apiDefineComponent'
4546
export { defineAsyncComponent } from './apiAsyncComponent'

packages/runtime-core/src/renderer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,12 @@ export const setRef = (
300300
return
301301
}
302302

303-
let value: ComponentPublicInstance | RendererNode | null
303+
let value: ComponentPublicInstance | RendererNode | Record<string, any> | null
304304
if (!vnode) {
305305
value = null
306306
} else {
307307
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
308-
value = vnode.component!.proxy
308+
value = vnode.component!.exposed || vnode.component!.proxy
309309
} else {
310310
value = vnode.el
311311
}

0 commit comments

Comments
 (0)