vue3-core/packages/runtime-vapor/src/component.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

318 lines
7.2 KiB
TypeScript
Raw Normal View History

import { EffectScope } from '@vue/reactivity'
import { EMPTY_OBJ, NOOP, isFunction } from '@vue/shared'
import type { Block } from './apiRender'
2024-02-25 15:23:29 +08:00
import type { DirectiveBinding } from './directives'
import {
type ComponentPropsOptions,
type NormalizedPropsOptions,
type NormalizedRawProps,
type RawProps,
initProps,
normalizePropsOptions,
} from './componentProps'
import {
type EmitFn,
type EmitsOptions,
type ObjectEmitsOptions,
emit,
normalizeEmitsOptions,
} from './componentEmits'
2024-03-14 15:50:58 +08:00
import { VaporLifecycleHooks } from './apiLifecycle'
import { warn } from './warning'
import { type AppContext, createAppContext } from './apiCreateVaporApp'
import type { Data } from '@vue/shared'
export type Component = FunctionalComponent | ObjectComponent
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,
}
}
}
2023-12-06 14:59:11 +08:00
export interface ObjectComponent {
props?: ComponentPropsOptions
inheritAttrs?: boolean
emits?: EmitsOptions
setup?: SetupFn
render?(ctx: any): Block
vapor?: boolean
2023-12-06 14:59:11 +08:00
}
type LifecycleHook<TFn = Function> = TFn[] | null
export const componentKey = Symbol(__DEV__ ? `componentKey` : ``)
export interface ComponentInternalInstance {
[componentKey]: true
uid: number
vapor: true
appContext: AppContext
block: Block | null
container: ParentNode
parent: ComponentInternalInstance | null
provides: Data
scope: EffectScope
2023-12-06 14:59:11 +08:00
component: FunctionalComponent | ObjectComponent
comps: Set<ComponentInternalInstance>
dirs: Map<Node, DirectiveBinding[]>
rawProps: NormalizedRawProps
propsOptions: NormalizedPropsOptions
emitsOptions: ObjectEmitsOptions | null
// state
setupState: Data
setupContext: SetupContext | null
props: Data
emit: EmitFn
emitted: Record<string, boolean> | null
attrs: Data
2024-01-21 13:59:56 +08:00
refs: Data
attrsProxy: Data | null
// lifecycle
isMounted: boolean
isUnmounted: boolean
isUpdating: boolean
// TODO: registory of provides, lifecycles, ...
/**
* @internal
*/
[VaporLifecycleHooks.BEFORE_MOUNT]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.MOUNTED]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.BEFORE_UPDATE]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.UPDATED]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.UNMOUNTED]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.RENDER_TRACKED]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.ACTIVATED]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.DEACTIVATED]: LifecycleHook
/**
* @internal
*/
[VaporLifecycleHooks.ERROR_CAPTURED]: LifecycleHook
/**
* @internal
*/
// [VaporLifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise<unknown>>
}
2023-12-03 18:36:01 +08:00
// TODO
export let currentInstance: ComponentInternalInstance | null = null
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
currentInstance
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
const prev = currentInstance
2023-12-03 18:36:01 +08:00
currentInstance = instance
instance.scope.on()
return () => {
instance.scope.off()
currentInstance = prev
}
2023-12-03 18:36:01 +08:00
}
export const unsetCurrentInstance = () => {
currentInstance?.scope.off()
2023-12-03 18:36:01 +08:00
currentInstance = null
}
const emptyAppContext = createAppContext()
let uid = 0
export function createComponentInstance(
2023-12-06 14:59:11 +08:00
component: ObjectComponent | FunctionalComponent,
rawProps: RawProps | null,
// application root node only
appContext: AppContext | null = null,
): ComponentInternalInstance {
const parent = getCurrentInstance()
const _appContext =
(parent ? parent.appContext : appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
[componentKey]: true,
uid: uid++,
vapor: true,
appContext: _appContext,
block: null,
container: null!,
parent,
scope: new EffectScope(true /* detached */)!,
provides: parent ? parent.provides : Object.create(_appContext.provides),
component,
comps: new Set(),
dirs: new Map(),
// resolved props and emits options
rawProps: null!, // set later
propsOptions: normalizePropsOptions(component),
emitsOptions: normalizeEmitsOptions(component),
// state
setupState: EMPTY_OBJ,
setupContext: null,
props: EMPTY_OBJ,
emit: null!,
emitted: null,
attrs: EMPTY_OBJ,
2024-01-21 13:59:56 +08:00
refs: EMPTY_OBJ,
attrsProxy: null,
// lifecycle
isMounted: false,
isUnmounted: false,
isUpdating: false,
// TODO: registory of provides, appContext, lifecycles, ...
/**
* @internal
*/
[VaporLifecycleHooks.BEFORE_MOUNT]: null,
/**
* @internal
*/
[VaporLifecycleHooks.MOUNTED]: null,
/**
* @internal
*/
[VaporLifecycleHooks.BEFORE_UPDATE]: null,
/**
* @internal
*/
[VaporLifecycleHooks.UPDATED]: null,
/**
* @internal
*/
[VaporLifecycleHooks.BEFORE_UNMOUNT]: null,
/**
* @internal
*/
[VaporLifecycleHooks.UNMOUNTED]: null,
/**
* @internal
*/
[VaporLifecycleHooks.RENDER_TRACKED]: null,
/**
* @internal
*/
[VaporLifecycleHooks.RENDER_TRIGGERED]: null,
/**
* @internal
*/
[VaporLifecycleHooks.ACTIVATED]: null,
/**
* @internal
*/
[VaporLifecycleHooks.DEACTIVATED]: null,
/**
* @internal
*/
[VaporLifecycleHooks.ERROR_CAPTURED]: null,
/**
* @internal
*/
// [VaporLifecycleHooks.SERVER_PREFETCH]: null,
}
initProps(instance, rawProps, !isFunction(component))
instance.emit = emit.bind(null, 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]
},
},
))
)
}