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 {
type ComponentInternalInstance,
componentKey,
createSetupContext,
setCurrentInstance,
} from './component'
import { insert, querySelector, remove } from './dom/element'
import { flushPostFlushCbs, queuePostRenderEffect } from './scheduler'
import { proxyRefs } from '@vue/reactivity'
import { invokeLifecycle } from './componentLifecycle'
import { VaporLifecycleHooks } from './apiLifecycle'
import {
pauseTracking,
proxyRefs,
resetTracking,
shallowReadonly,
} from '@vue/reactivity'
import { isArray, isFunction, isObject } from '@vue/shared'
import { fallThroughAttrs } from './componentAttrs'
import { VaporErrorCodes, callWithErrorHandling } from './errorHandling'
export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
@ -26,11 +33,22 @@ export function setupComponent(
): void {
const reset = setCurrentInstance(instance)
instance.scope.run(() => {
const { component, props, emit, attrs } = instance
const ctx = { expose: () => {}, emit, attrs }
const { component, props } = instance
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

View File

@ -1,5 +1,5 @@
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 { DirectiveBinding } from './directives'
import {
@ -20,12 +20,48 @@ import {
import { VaporLifecycleHooks } from './apiLifecycle'
import type { Data } from '@vue/shared'
import { warn } from './warning'
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 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 {
props?: ComponentPropsOptions
inheritAttrs?: boolean
@ -59,12 +95,15 @@ export interface ComponentInternalInstance {
// state
setupState: Data
setupContext: SetupContext | null
props: Data
emit: EmitFn
emitted: Record<string, boolean> | null
attrs: Data
refs: Data
attrsProxy: Data | null
// lifecycle
isMounted: boolean
isUnmounted: boolean
@ -169,12 +208,15 @@ export function createComponentInstance(
// state
setupState: EMPTY_OBJ,
setupContext: null,
props: EMPTY_OBJ,
emit: null!,
emitted: null,
attrs: EMPTY_OBJ,
refs: EMPTY_OBJ,
attrsProxy: null,
// lifecycle
isMounted: false,
isUnmounted: false,
@ -234,3 +276,31 @@ export function createComponentInstance(
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]
},
},
))
)
}