feat(runtime-vapor): implement setupContext (#157)

This commit is contained in:
Doctor Wu 2024-03-22 23:28:18 +08:00 committed by GitHub
parent 38e167ceb8
commit 9a2c12e3cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 95 additions and 7 deletions

View File

@ -1,15 +1,22 @@
import { isArray, isFunction, isObject } from '@vue/shared'
import { import {
type ComponentInternalInstance, type ComponentInternalInstance,
componentKey, componentKey,
createSetupContext,
setCurrentInstance, setCurrentInstance,
} from './component' } from './component'
import { insert, querySelector, remove } from './dom/element' import { insert, querySelector, remove } from './dom/element'
import { flushPostFlushCbs, queuePostRenderEffect } from './scheduler' import { flushPostFlushCbs, queuePostRenderEffect } from './scheduler'
import { proxyRefs } from '@vue/reactivity'
import { invokeLifecycle } from './componentLifecycle' import { invokeLifecycle } from './componentLifecycle'
import { VaporLifecycleHooks } from './apiLifecycle' import { VaporLifecycleHooks } from './apiLifecycle'
import {
pauseTracking,
proxyRefs,
resetTracking,
shallowReadonly,
} from '@vue/reactivity'
import { isArray, isFunction, isObject } from '@vue/shared'
import { fallThroughAttrs } from './componentAttrs' import { fallThroughAttrs } from './componentAttrs'
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``) export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
@ -26,11 +33,22 @@ export function setupComponent(
): void { ): void {
const reset = setCurrentInstance(instance) const reset = setCurrentInstance(instance)
instance.scope.run(() => { instance.scope.run(() => {
const { component, props, emit, attrs } = instance const { component, props } = instance
const ctx = { expose: () => {}, emit, attrs }
const setupFn = isFunction(component) ? component : component.setup const setupFn = isFunction(component) ? component : component.setup
const stateOrNode = setupFn && setupFn(props, ctx) let stateOrNode: Block | undefined
if (setupFn) {
const setupContext = (instance.setupContext =
setupFn && setupFn.length > 1 ? createSetupContext(instance) : null)
pauseTracking()
stateOrNode = callWithErrorHandling(
setupFn,
instance,
VaporErrorCodes.SETUP_FUNCTION,
[__DEV__ ? shallowReadonly(props) : props, setupContext],
)
resetTracking()
}
let block: Block | undefined let block: Block | undefined

View File

@ -1,5 +1,5 @@
import { EffectScope } from '@vue/reactivity' import { EffectScope } from '@vue/reactivity'
import { EMPTY_OBJ, isFunction } from '@vue/shared' import { EMPTY_OBJ, NOOP, isFunction } from '@vue/shared'
import type { Block } from './apiRender' import type { Block } from './apiRender'
import type { DirectiveBinding } from './directives' import type { DirectiveBinding } from './directives'
import { import {
@ -20,12 +20,48 @@ import {
import { VaporLifecycleHooks } from './apiLifecycle' import { VaporLifecycleHooks } from './apiLifecycle'
import type { Data } from '@vue/shared' import type { Data } from '@vue/shared'
import { warn } from './warning'
export type Component = FunctionalComponent | ObjectComponent export type Component = FunctionalComponent | ObjectComponent
export type SetupFn = (props: any, ctx: any) => Block | Data | void export type SetupFn = (props: any, ctx: SetupContext) => Block | Data | void
export type FunctionalComponent = SetupFn & Omit<ObjectComponent, 'setup'> export type FunctionalComponent = SetupFn & Omit<ObjectComponent, 'setup'>
export type SetupContext<E = EmitsOptions> = E extends any
? {
attrs: Data
emit: EmitFn<E>
expose: (exposed?: Record<string, any>) => void
// TODO slots
}
: never
export function createSetupContext(
instance: ComponentInternalInstance,
): SetupContext {
if (__DEV__) {
// We use getters in dev in case libs like test-utils overwrite instance
// properties (overwrites should not be done in prod)
return Object.freeze({
get attrs() {
return getAttrsProxy(instance)
},
get emit() {
return (event: string, ...args: any[]) => instance.emit(event, ...args)
},
expose: NOOP,
})
} else {
return {
get attrs() {
return getAttrsProxy(instance)
},
emit: instance.emit,
expose: NOOP,
}
}
}
export interface ObjectComponent { export interface ObjectComponent {
props?: ComponentPropsOptions props?: ComponentPropsOptions
inheritAttrs?: boolean inheritAttrs?: boolean
@ -59,12 +95,15 @@ export interface ComponentInternalInstance {
// state // state
setupState: Data setupState: Data
setupContext: SetupContext | null
props: Data props: Data
emit: EmitFn emit: EmitFn
emitted: Record<string, boolean> | null emitted: Record<string, boolean> | null
attrs: Data attrs: Data
refs: Data refs: Data
attrsProxy: Data | null
// lifecycle // lifecycle
isMounted: boolean isMounted: boolean
isUnmounted: boolean isUnmounted: boolean
@ -169,12 +208,15 @@ export function createComponentInstance(
// state // state
setupState: EMPTY_OBJ, setupState: EMPTY_OBJ,
setupContext: null,
props: EMPTY_OBJ, props: EMPTY_OBJ,
emit: null!, emit: null!,
emitted: null, emitted: null,
attrs: EMPTY_OBJ, attrs: EMPTY_OBJ,
refs: EMPTY_OBJ, refs: EMPTY_OBJ,
attrsProxy: null,
// lifecycle // lifecycle
isMounted: false, isMounted: false,
isUnmounted: false, isUnmounted: false,
@ -234,3 +276,31 @@ export function createComponentInstance(
return instance return instance
} }
function getAttrsProxy(instance: ComponentInternalInstance): Data {
return (
instance.attrsProxy ||
(instance.attrsProxy = new Proxy(
instance.attrs,
__DEV__
? {
get(target, key: string) {
return target[key]
},
set() {
warn(`setupContext.attrs is readonly.`)
return false
},
deleteProperty() {
warn(`setupContext.attrs is readonly.`)
return false
},
}
: {
get(target, key: string) {
return target[key]
},
},
))
)
}