Skip to content

Commit d490bf2

Browse files
Doctor-wusxzz
andauthored
feat(runtime-vapor): implement expose (#181)
Co-authored-by: Kevin Deng 三咲智子 <[email protected]>
1 parent e67e643 commit d490bf2

File tree

2 files changed

+80
-4
lines changed

2 files changed

+80
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { describe, expect } from 'vitest'
2+
import { makeRender } from './_utils'
3+
import { type Ref, ref } from '@vue/reactivity'
4+
5+
const define = makeRender()
6+
7+
describe('component expose', () => {
8+
test('should work', async () => {
9+
const expxosedObj = { foo: 1 }
10+
const { render } = define({
11+
setup(_, { expose }) {
12+
expose(expxosedObj)
13+
},
14+
})
15+
const { instance } = render()
16+
expect(instance.exposed).toEqual(expxosedObj)
17+
})
18+
19+
test('should warn when called multiple times', async () => {
20+
const { render } = define({
21+
setup(_, { expose }) {
22+
expose()
23+
expose()
24+
},
25+
})
26+
render()
27+
expect(
28+
'expose() should be called only once per setup().',
29+
).toHaveBeenWarned()
30+
})
31+
32+
test('should warn when passed non-object', async () => {
33+
const exposedRef = ref<number[] | Ref>([1, 2, 3])
34+
const { render } = define({
35+
setup(_, { expose }) {
36+
expose(exposedRef.value)
37+
},
38+
})
39+
render()
40+
expect(
41+
'expose() should be passed a plain object, received array.',
42+
).toHaveBeenWarned()
43+
exposedRef.value = ref(1)
44+
render()
45+
expect(
46+
'expose() should be passed a plain object, received ref.',
47+
).toHaveBeenWarned()
48+
})
49+
})

packages/runtime-vapor/src/component.ts

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { EffectScope } from '@vue/reactivity'
2-
import { EMPTY_OBJ, NOOP, isFunction } from '@vue/shared'
1+
import { EffectScope, isRef } from '@vue/reactivity'
2+
import { EMPTY_OBJ, isArray, isFunction } from '@vue/shared'
33
import type { Block } from './apiRender'
44
import type { DirectiveBinding } from './directives'
55
import {
@@ -45,6 +45,30 @@ export type SetupContext<E = EmitsOptions> = E extends any
4545
export function createSetupContext(
4646
instance: ComponentInternalInstance,
4747
): SetupContext {
48+
const expose: SetupContext['expose'] = exposed => {
49+
if (__DEV__) {
50+
if (instance.exposed) {
51+
warn(`expose() should be called only once per setup().`)
52+
}
53+
if (exposed != null) {
54+
let exposedType: string = typeof exposed
55+
if (exposedType === 'object') {
56+
if (isArray(exposed)) {
57+
exposedType = 'array'
58+
} else if (isRef(exposed)) {
59+
exposedType = 'ref'
60+
}
61+
}
62+
if (exposedType !== 'object') {
63+
warn(
64+
`expose() should be passed a plain object, received ${exposedType}.`,
65+
)
66+
}
67+
}
68+
}
69+
instance.exposed = exposed || {}
70+
}
71+
4872
if (__DEV__) {
4973
// We use getters in dev in case libs like test-utils overwrite instance
5074
// properties (overwrites should not be done in prod)
@@ -58,7 +82,7 @@ export function createSetupContext(
5882
get emit() {
5983
return (event: string, ...args: any[]) => instance.emit(event, ...args)
6084
},
61-
expose: NOOP,
85+
expose,
6286
})
6387
} else {
6488
return {
@@ -67,7 +91,7 @@ export function createSetupContext(
6791
},
6892
emit: instance.emit,
6993
slots: instance.slots,
70-
expose: NOOP,
94+
expose,
7195
}
7296
}
7397
}
@@ -114,9 +138,12 @@ export interface ComponentInternalInstance {
114138
attrs: Data
115139
slots: InternalSlots
116140
refs: Data
141+
// exposed properties via expose()
142+
exposed?: Record<string, any>
117143

118144
attrsProxy?: Data
119145
slotsProxy?: Slots
146+
exposeProxy?: Record<string, any>
120147

121148
// lifecycle
122149
isMounted: boolean

0 commit comments

Comments
 (0)