Skip to content

Commit 9a2c12e

Browse files
authored
feat(runtime-vapor): implement setupContext (#157)
1 parent 38e167c commit 9a2c12e

File tree

2 files changed

+95
-7
lines changed

2 files changed

+95
-7
lines changed

packages/runtime-vapor/src/apiRender.ts

+23-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
1-
import { isArray, isFunction, isObject } from '@vue/shared'
21
import {
32
type ComponentInternalInstance,
43
componentKey,
4+
createSetupContext,
55
setCurrentInstance,
66
} from './component'
77
import { insert, querySelector, remove } from './dom/element'
88
import { flushPostFlushCbs, queuePostRenderEffect } from './scheduler'
9-
import { proxyRefs } from '@vue/reactivity'
109
import { invokeLifecycle } from './componentLifecycle'
1110
import { VaporLifecycleHooks } from './apiLifecycle'
11+
import {
12+
pauseTracking,
13+
proxyRefs,
14+
resetTracking,
15+
shallowReadonly,
16+
} from '@vue/reactivity'
17+
import { isArray, isFunction, isObject } from '@vue/shared'
1218
import { fallThroughAttrs } from './componentAttrs'
19+
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
1320

1421
export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
1522

@@ -26,11 +33,22 @@ export function setupComponent(
2633
): void {
2734
const reset = setCurrentInstance(instance)
2835
instance.scope.run(() => {
29-
const { component, props, emit, attrs } = instance
30-
const ctx = { expose: () => {}, emit, attrs }
36+
const { component, props } = instance
3137

3238
const setupFn = isFunction(component) ? component : component.setup
33-
const stateOrNode = setupFn && setupFn(props, ctx)
39+
let stateOrNode: Block | undefined
40+
if (setupFn) {
41+
const setupContext = (instance.setupContext =
42+
setupFn && setupFn.length > 1 ? createSetupContext(instance) : null)
43+
pauseTracking()
44+
stateOrNode = callWithErrorHandling(
45+
setupFn,
46+
instance,
47+
VaporErrorCodes.SETUP_FUNCTION,
48+
[__DEV__ ? shallowReadonly(props) : props, setupContext],
49+
)
50+
resetTracking()
51+
}
3452

3553
let block: Block | undefined
3654

packages/runtime-vapor/src/component.ts

+72-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EffectScope } from '@vue/reactivity'
2-
import { EMPTY_OBJ, isFunction } from '@vue/shared'
2+
import { EMPTY_OBJ, NOOP, isFunction } from '@vue/shared'
33
import type { Block } from './apiRender'
44
import type { DirectiveBinding } from './directives'
55
import {
@@ -20,12 +20,48 @@ import {
2020
import { VaporLifecycleHooks } from './apiLifecycle'
2121

2222
import type { Data } from '@vue/shared'
23+
import { warn } from './warning'
2324

2425
export type Component = FunctionalComponent | ObjectComponent
2526

26-
export type SetupFn = (props: any, ctx: any) => Block | Data | void
27+
export type SetupFn = (props: any, ctx: SetupContext) => Block | Data | void
2728
export type FunctionalComponent = SetupFn & Omit<ObjectComponent, 'setup'>
2829

30+
export type SetupContext<E = EmitsOptions> = E extends any
31+
? {
32+
attrs: Data
33+
emit: EmitFn<E>
34+
expose: (exposed?: Record<string, any>) => void
35+
// TODO slots
36+
}
37+
: never
38+
39+
export function createSetupContext(
40+
instance: ComponentInternalInstance,
41+
): SetupContext {
42+
if (__DEV__) {
43+
// We use getters in dev in case libs like test-utils overwrite instance
44+
// properties (overwrites should not be done in prod)
45+
return Object.freeze({
46+
get attrs() {
47+
return getAttrsProxy(instance)
48+
},
49+
get emit() {
50+
return (event: string, ...args: any[]) => instance.emit(event, ...args)
51+
},
52+
expose: NOOP,
53+
})
54+
} else {
55+
return {
56+
get attrs() {
57+
return getAttrsProxy(instance)
58+
},
59+
emit: instance.emit,
60+
expose: NOOP,
61+
}
62+
}
63+
}
64+
2965
export interface ObjectComponent {
3066
props?: ComponentPropsOptions
3167
inheritAttrs?: boolean
@@ -59,12 +95,15 @@ export interface ComponentInternalInstance {
5995

6096
// state
6197
setupState: Data
98+
setupContext: SetupContext | null
6299
props: Data
63100
emit: EmitFn
64101
emitted: Record<string, boolean> | null
65102
attrs: Data
66103
refs: Data
67104

105+
attrsProxy: Data | null
106+
68107
// lifecycle
69108
isMounted: boolean
70109
isUnmounted: boolean
@@ -169,12 +208,15 @@ export function createComponentInstance(
169208

170209
// state
171210
setupState: EMPTY_OBJ,
211+
setupContext: null,
172212
props: EMPTY_OBJ,
173213
emit: null!,
174214
emitted: null,
175215
attrs: EMPTY_OBJ,
176216
refs: EMPTY_OBJ,
177217

218+
attrsProxy: null,
219+
178220
// lifecycle
179221
isMounted: false,
180222
isUnmounted: false,
@@ -234,3 +276,31 @@ export function createComponentInstance(
234276

235277
return instance
236278
}
279+
280+
function getAttrsProxy(instance: ComponentInternalInstance): Data {
281+
return (
282+
instance.attrsProxy ||
283+
(instance.attrsProxy = new Proxy(
284+
instance.attrs,
285+
__DEV__
286+
? {
287+
get(target, key: string) {
288+
return target[key]
289+
},
290+
set() {
291+
warn(`setupContext.attrs is readonly.`)
292+
return false
293+
},
294+
deleteProperty() {
295+
warn(`setupContext.attrs is readonly.`)
296+
return false
297+
},
298+
}
299+
: {
300+
get(target, key: string) {
301+
return target[key]
302+
},
303+
},
304+
))
305+
)
306+
}

0 commit comments

Comments
 (0)