wip: unify currentInstance between vdom and vapor + provide/inject

This commit is contained in:
Evan You 2024-12-05 23:13:24 +08:00
parent ee7a93df27
commit e23a6a8746
No known key found for this signature in database
GPG Key ID: 00E9AB7A6704CE0A
9 changed files with 131 additions and 98 deletions

View File

@ -1,6 +1,5 @@
import { isFunction } from '@vue/shared' import { isFunction } from '@vue/shared'
import { currentInstance } from './component' import { getCurrentGenericInstance } from './component'
import { currentRenderingInstance } from './componentRenderContext'
import { currentApp } from './apiCreateApp' import { currentApp } from './apiCreateApp'
import { warn } from './warning' import { warn } from './warning'
@ -12,6 +11,7 @@ export function provide<T, K = InjectionKey<T> | string | number>(
key: K, key: K,
value: K extends InjectionKey<infer V> ? V : T, value: K extends InjectionKey<infer V> ? V : T,
): void { ): void {
const currentInstance = getCurrentGenericInstance()
if (!currentInstance) { if (!currentInstance) {
if (__DEV__) { if (__DEV__) {
warn(`provide() can only be used inside setup().`) warn(`provide() can only be used inside setup().`)
@ -51,7 +51,7 @@ export function inject(
) { ) {
// fallback to `currentRenderingInstance` so that this can be called in // fallback to `currentRenderingInstance` so that this can be called in
// a functional component // a functional component
const instance = currentInstance || currentRenderingInstance const instance = getCurrentGenericInstance()
// also support looking up from app-level provides w/ `app.runWithContext()` // also support looking up from app-level provides w/ `app.runWithContext()`
if (instance || currentApp) { if (instance || currentApp) {
@ -63,7 +63,7 @@ export function inject(
? currentApp._context.provides ? currentApp._context.provides
: instance : instance
? instance.parent == null ? instance.parent == null
? instance.vnode.appContext && instance.vnode.appContext.provides ? instance.appContext && instance.appContext.provides
: instance.parent.provides : instance.parent.provides
: undefined : undefined
@ -88,5 +88,5 @@ export function inject(
* user. One example is `useRoute()` in `vue-router`. * user. One example is `useRoute()` in `vue-router`.
*/ */
export function hasInjectionContext(): boolean { export function hasInjectionContext(): boolean {
return !!(currentInstance || currentRenderingInstance || currentApp) return !!(getCurrentGenericInstance() || currentApp)
} }

View File

@ -1,5 +1,5 @@
import { import {
type ComponentInternalInstance, type GenericComponentInstance,
currentInstance, currentInstance,
isInSSRComponentSetup, isInSSRComponentSetup,
setCurrentInstance, setCurrentInstance,
@ -20,7 +20,7 @@ export { onActivated, onDeactivated } from './components/KeepAlive'
export function injectHook( export function injectHook(
type: LifecycleHooks, type: LifecycleHooks,
hook: Function & { __weh?: Function }, hook: Function & { __weh?: Function },
target: ComponentInternalInstance | null = currentInstance, target: GenericComponentInstance | null = currentInstance,
prepend: boolean = false, prepend: boolean = false,
): Function | undefined { ): Function | undefined {
if (target) { if (target) {
@ -67,7 +67,7 @@ const createHook =
<T extends Function = () => any>(lifecycle: LifecycleHooks) => <T extends Function = () => any>(lifecycle: LifecycleHooks) =>
( (
hook: T, hook: T,
target: ComponentInternalInstance | null = currentInstance, target: GenericComponentInstance | null = currentInstance,
): void => { ): void => {
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch) // post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
if ( if (
@ -79,7 +79,7 @@ const createHook =
} }
type CreateHook<T = any> = ( type CreateHook<T = any> = (
hook: T, hook: T,
target?: ComponentInternalInstance | null, target?: GenericComponentInstance | null,
) => void ) => void
export const onBeforeMount: CreateHook = createHook(LifecycleHooks.BEFORE_MOUNT) export const onBeforeMount: CreateHook = createHook(LifecycleHooks.BEFORE_MOUNT)
@ -110,7 +110,7 @@ export type ErrorCapturedHook<TError = unknown> = (
export function onErrorCaptured<TError = Error>( export function onErrorCaptured<TError = Error>(
hook: ErrorCapturedHook<TError>, hook: ErrorCapturedHook<TError>,
target: ComponentInternalInstance | null = currentInstance, target: GenericComponentInstance | null = currentInstance,
): void { ): void {
injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target) injectHook(LifecycleHooks.ERROR_CAPTURED, hook, target)
} }

View File

@ -332,6 +332,7 @@ export type InternalRenderFunction = {
* operate on both. * operate on both.
*/ */
export interface GenericComponentInstance { export interface GenericComponentInstance {
vapor?: boolean
uid: number uid: number
type: GenericComponent type: GenericComponent
parent: GenericComponentInstance | null parent: GenericComponentInstance | null
@ -395,10 +396,64 @@ export interface GenericComponentInstance {
// the following are for error handling logic only // the following are for error handling logic only
proxy?: any proxy?: any
// lifecycle
/** /**
* @internal * @internal
*/ */
[LifecycleHooks.ERROR_CAPTURED]: LifecycleHook [LifecycleHooks.BEFORE_CREATE]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.CREATED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_MOUNT]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.MOUNTED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_UPDATE]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.UPDATED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_UNMOUNT]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.UNMOUNTED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.RENDER_TRACKED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.RENDER_TRIGGERED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.ACTIVATED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.DEACTIVATED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.ERROR_CAPTURED]?: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.SERVER_PREFETCH]?: LifecycleHook<() => Promise<unknown>>
} }
/** /**
@ -406,6 +461,7 @@ export interface GenericComponentInstance {
* useful for advanced external libraries and tools. * useful for advanced external libraries and tools.
*/ */
export interface ComponentInternalInstance extends GenericComponentInstance { export interface ComponentInternalInstance extends GenericComponentInstance {
vapor?: never
uid: number uid: number
type: ConcreteComponent type: ConcreteComponent
parent: ComponentInternalInstance | null parent: ComponentInternalInstance | null
@ -563,64 +619,6 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
*/ */
asyncResolved: boolean asyncResolved: boolean
// lifecycle
/**
* @internal
*/
[LifecycleHooks.BEFORE_CREATE]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.CREATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_MOUNT]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.MOUNTED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_UPDATE]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.UPDATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.BEFORE_UNMOUNT]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.UNMOUNTED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.RENDER_TRACKED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.RENDER_TRIGGERED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.ACTIVATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.DEACTIVATED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.ERROR_CAPTURED]: LifecycleHook
/**
* @internal
*/
[LifecycleHooks.SERVER_PREFETCH]: LifecycleHook<() => Promise<unknown>>
/** /**
* For caching bound $forceUpdate on public proxy access * For caching bound $forceUpdate on public proxy access
* @internal * @internal

View File

@ -1,18 +1,32 @@
import { getGlobalThis } from '@vue/shared' import { getGlobalThis } from '@vue/shared'
import type { ComponentInternalInstance } from './component' import type {
ComponentInternalInstance,
GenericComponentInstance,
} from './component'
import { currentRenderingInstance } from './componentRenderContext' import { currentRenderingInstance } from './componentRenderContext'
export let currentInstance: ComponentInternalInstance | null = null /**
* @internal
*/
export let currentInstance: GenericComponentInstance | null = null
/**
* @internal
*/
export const getCurrentGenericInstance: () => GenericComponentInstance | null =
() => currentInstance || currentRenderingInstance
export const getCurrentInstance: () => ComponentInternalInstance | null = () => export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
currentInstance || currentRenderingInstance currentInstance && !currentInstance.vapor
? (currentInstance as ComponentInternalInstance)
: currentRenderingInstance
export let isInSSRComponentSetup = false export let isInSSRComponentSetup = false
export let setInSSRSetupState: (state: boolean) => void export let setInSSRSetupState: (state: boolean) => void
let internalSetCurrentInstance: ( let internalSetCurrentInstance: (
instance: ComponentInternalInstance | null, instance: GenericComponentInstance | null,
) => void ) => void
/** /**
@ -60,7 +74,10 @@ if (__SSR__) {
} }
} }
export const setCurrentInstance = (instance: ComponentInternalInstance) => { /**
* @internal
*/
export const setCurrentInstance = (instance: GenericComponentInstance) => {
const prev = currentInstance const prev = currentInstance
internalSetCurrentInstance(instance) internalSetCurrentInstance(instance)
instance.scope.on() instance.scope.on()

View File

@ -6,7 +6,7 @@ import {
type Data, type Data,
type InternalRenderFunction, type InternalRenderFunction,
type SetupContext, type SetupContext,
currentInstance, getCurrentInstance,
} from './component' } from './component'
import { import {
type LooseRequired, type LooseRequired,
@ -855,10 +855,8 @@ export function createWatcher(
const options: WatchOptions = {} const options: WatchOptions = {}
if (__COMPAT__) { if (__COMPAT__) {
const instance = const cur = getCurrentInstance()
currentInstance && getCurrentScope() === currentInstance.scope const instance = cur && getCurrentScope() === cur.scope ? cur : null
? currentInstance
: null
const newValue = getter() const newValue = getter()
if ( if (

View File

@ -3,7 +3,6 @@ import {
type ComponentOptions, type ComponentOptions,
type ConcreteComponent, type ConcreteComponent,
type SetupContext, type SetupContext,
currentInstance,
getComponentName, getComponentName,
getCurrentInstance, getCurrentInstance,
} from '../component' } from '../component'
@ -411,7 +410,7 @@ export function onDeactivated(
function registerKeepAliveHook( function registerKeepAliveHook(
hook: Function & { __wdc?: Function }, hook: Function & { __wdc?: Function },
type: LifecycleHooks, type: LifecycleHooks,
target: ComponentInternalInstance | null = currentInstance, target: ComponentInternalInstance | null = getCurrentInstance(),
) { ) {
// cache the deactivate branch check wrapper for injected hooks so the same // cache the deactivate branch check wrapper for injected hooks so the same
// hook can be properly deduped by the scheduler. "__wdc" stands for "with // hook can be properly deduped by the scheduler. "__wdc" stands for "with

View File

@ -507,3 +507,4 @@ export {
type AppMountFn, type AppMountFn,
type AppUnmountFn, type AppUnmountFn,
} from './apiCreateApp' } from './apiCreateApp'
export { currentInstance, setCurrentInstance } from './componentCurrentInstance'

View File

@ -9,9 +9,11 @@ import {
type LifecycleHook, type LifecycleHook,
type NormalizedPropsOptions, type NormalizedPropsOptions,
type ObjectEmitsOptions, type ObjectEmitsOptions,
currentInstance,
nextUid, nextUid,
popWarningContext, popWarningContext,
pushWarningContext, pushWarningContext,
setCurrentInstance,
warn, warn,
} from '@vue/runtime-dom' } from '@vue/runtime-dom'
import { type Block, isBlock } from './block' import { type Block, isBlock } from './block'
@ -28,6 +30,8 @@ import { setDynamicProp } from './dom/prop'
import { renderEffect } from './renderEffect' import { renderEffect } from './renderEffect'
import { emit, normalizeEmitsOptions } from './componentEmits' import { emit, normalizeEmitsOptions } from './componentEmits'
export { currentInstance } from '@vue/runtime-dom'
export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent export type VaporComponent = FunctionalVaporComponent | ObjectVaporComponent
export type VaporSetupFn = ( export type VaporSetupFn = (
@ -77,7 +81,11 @@ export function createComponent(
): VaporComponentInstance { ): VaporComponentInstance {
// check if we are the single root of the parent // check if we are the single root of the parent
// if yes, inject parent attrs as dynamic props source // if yes, inject parent attrs as dynamic props source
if (isSingleRoot && currentInstance && currentInstance.hasFallthrough) { if (
isSingleRoot &&
isVaporComponent(currentInstance) &&
currentInstance.hasFallthrough
) {
const attrs = currentInstance.attrs const attrs = currentInstance.attrs
if (rawProps) { if (rawProps) {
;(rawProps.$ || (rawProps.$ = [])).push(() => attrs) ;(rawProps.$ || (rawProps.$ = [])).push(() => attrs)
@ -87,12 +95,9 @@ export function createComponent(
} }
const instance = new VaporComponentInstance(component, rawProps) const instance = new VaporComponentInstance(component, rawProps)
const resetCurrentInstance = setCurrentInstance(instance)
pauseTracking() pauseTracking()
let prevInstance = currentInstance
currentInstance = instance
instance.scope.on()
if (__DEV__) { if (__DEV__) {
pushWarningContext(instance) pushWarningContext(instance)
} }
@ -140,15 +145,12 @@ export function createComponent(
if (__DEV__) { if (__DEV__) {
popWarningContext() popWarningContext()
} }
instance.scope.off()
currentInstance = prevInstance
resetTracking() resetTracking()
resetCurrentInstance()
return instance return instance
} }
export let currentInstance: VaporComponentInstance | null = null
const emptyContext: GenericAppContext = { const emptyContext: GenericAppContext = {
app: null as any, app: null as any,
config: {}, config: {},
@ -156,6 +158,7 @@ const emptyContext: GenericAppContext = {
} }
export class VaporComponentInstance implements GenericComponentInstance { export class VaporComponentInstance implements GenericComponentInstance {
vapor: true
uid: number uid: number
type: VaporComponent type: VaporComponent
parent: GenericComponentInstance | null parent: GenericComponentInstance | null
@ -178,11 +181,25 @@ export class VaporComponentInstance implements GenericComponentInstance {
hasFallthrough: boolean hasFallthrough: boolean
// lifecycle hooks
isMounted: boolean isMounted: boolean
isUnmounted: boolean isUnmounted: boolean
isDeactivated: boolean isDeactivated: boolean
// LifecycleHooks.ERROR_CAPTURED
ec: LifecycleHook bc?: LifecycleHook // LifecycleHooks.BEFORE_CREATE
c?: LifecycleHook // LifecycleHooks.CREATED
bm?: LifecycleHook // LifecycleHooks.BEFORE_MOUNT
m?: LifecycleHook // LifecycleHooks.MOUNTED
bu?: LifecycleHook // LifecycleHooks.BEFORE_UPDATE
u?: LifecycleHook // LifecycleHooks.UPDATED
um?: LifecycleHook // LifecycleHooks.BEFORE_UNMOUNT
bum?: LifecycleHook // LifecycleHooks.UNMOUNTED
da?: LifecycleHook // LifecycleHooks.DEACTIVATED
a?: LifecycleHook // LifecycleHooks.ACTIVATED
rtg?: LifecycleHook // LifecycleHooks.RENDER_TRACKED
rtc?: LifecycleHook // LifecycleHooks.RENDER_TRIGGERED
ec?: LifecycleHook // LifecycleHooks.ERROR_CAPTURED
sp?: LifecycleHook<() => Promise<unknown>> // LifecycleHooks.SERVER_PREFETCH
// dev only // dev only
setupState?: Record<string, any> setupState?: Record<string, any>
@ -190,9 +207,10 @@ export class VaporComponentInstance implements GenericComponentInstance {
emitsOptions?: ObjectEmitsOptions | null emitsOptions?: ObjectEmitsOptions | null
constructor(comp: VaporComponent, rawProps?: RawProps) { constructor(comp: VaporComponent, rawProps?: RawProps) {
this.vapor = true
this.uid = nextUid() this.uid = nextUid()
this.type = comp this.type = comp
this.parent = currentInstance this.parent = currentInstance // TODO when inside
this.appContext = currentInstance this.appContext = currentInstance
? currentInstance.appContext ? currentInstance.appContext
: emptyContext : emptyContext
@ -200,14 +218,17 @@ export class VaporComponentInstance implements GenericComponentInstance {
this.block = null! // to be set this.block = null! // to be set
this.scope = new EffectScope(true) this.scope = new EffectScope(true)
this.rawProps = rawProps this.provides = currentInstance
this.provides = this.refs = EMPTY_OBJ ? currentInstance.provides
: Object.create(this.appContext.provides)
this.refs = EMPTY_OBJ
this.emitted = this.ec = this.exposed = this.propsDefaults = null this.emitted = this.ec = this.exposed = this.propsDefaults = null
this.isMounted = this.isUnmounted = this.isDeactivated = false this.isMounted = this.isUnmounted = this.isDeactivated = false
// init props // init props
const target = rawProps || EMPTY_OBJ const target = rawProps || EMPTY_OBJ
const handlers = getPropsProxyHandlers(comp, this) const handlers = getPropsProxyHandlers(comp, this)
this.rawProps = rawProps
this.props = comp.props ? new Proxy(target, handlers[0]!) : {} this.props = comp.props ? new Proxy(target, handlers[0]!) : {}
this.attrs = new Proxy(target, handlers[1]) this.attrs = new Proxy(target, handlers[1])
this.hasFallthrough = hasFallthroughAttrs(comp, rawProps) this.hasFallthrough = hasFallthroughAttrs(comp, rawProps)

View File

@ -1,6 +1,5 @@
import { ReactiveEffect } from '@vue/reactivity' import { ReactiveEffect } from '@vue/reactivity'
import { type SchedulerJob, queueJob } from '@vue/runtime-dom' import { type SchedulerJob, currentInstance, queueJob } from '@vue/runtime-dom'
import { currentInstance } from './component'
export function renderEffect(fn: () => void): void { export function renderEffect(fn: () => void): void {
const updateFn = () => { const updateFn = () => {