From: Evan You Date: Tue, 3 Dec 2024 14:49:28 +0000 (+0800) Subject: wip: emits X-Git-Tag: v3.6.0-alpha.1~16^2~247 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=65fc9769f27e5a412ee757c12ee20b70e3ce3ad4;p=thirdparty%2Fvuejs%2Fcore.git wip: emits --- diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index ca6419dce3..b5cad12eab 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -366,6 +366,23 @@ export interface GenericComponentInstance { */ propsDefaults: Data | null + // lifecycle + isMounted: boolean + isUnmounted: boolean + isDeactivated: boolean + + // for vapor the following two are dev only + /** + * resolved props options + * @internal + */ + propsOptions?: NormalizedPropsOptions + /** + * resolved emits options + * @internal + */ + emitsOptions?: ObjectEmitsOptions | null + // the following are for error handling logic only proxy?: any /** @@ -538,9 +555,6 @@ export interface ComponentInternalInstance extends GenericComponentInstance { asyncResolved: boolean // lifecycle - isMounted: boolean - isUnmounted: boolean - isDeactivated: boolean /** * @internal */ diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index abeb74a6d4..6ce9f371a0 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -18,6 +18,7 @@ import { type ComponentInternalInstance, type ComponentOptions, type ConcreteComponent, + type GenericComponentInstance, formatComponentName, } from './component' import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling' @@ -114,13 +115,27 @@ export function emit( ...rawArgs: any[] ): ComponentPublicInstance | null | undefined { if (instance.isUnmounted) return - const props = instance.vnode.props || EMPTY_OBJ + return baseEmit( + instance, + instance.vnode.props || EMPTY_OBJ, + defaultPropGetter, + event, + ...rawArgs, + ) +} +/** + * @internal for vapor only + */ +export function baseEmit( + instance: GenericComponentInstance, + props: Record, + getter: (props: Record, key: string) => unknown, + event: string, + ...rawArgs: any[] +): ComponentPublicInstance | null | undefined { if (__DEV__) { - const { - emitsOptions, - propsOptions: [propsOptions], - } = instance + const { emitsOptions, propsOptions } = instance if (emitsOptions) { if ( !(event in emitsOptions) && @@ -130,7 +145,11 @@ export function emit( event.startsWith(compatModelEventPrefix)) ) ) { - if (!propsOptions || !(toHandlerKey(camelize(event)) in propsOptions)) { + if ( + !propsOptions || + !propsOptions[0] || + !(toHandlerKey(camelize(event)) in propsOptions[0]) + ) { warn( `Component emitted event "${event}" but it is neither declared in ` + `the emits option nor as an "${toHandlerKey(camelize(event))}" prop.`, @@ -170,7 +189,10 @@ export function emit( if (__DEV__) { const lowerCaseEvent = event.toLowerCase() - if (lowerCaseEvent !== event && props[toHandlerKey(lowerCaseEvent)]) { + if ( + lowerCaseEvent !== event && + getter(props, toHandlerKey(lowerCaseEvent)) + ) { warn( `Event "${lowerCaseEvent}" is emitted in component ` + `${formatComponentName( @@ -188,18 +210,18 @@ export function emit( let handlerName let handler = - props[(handlerName = toHandlerKey(event))] || + getter(props, (handlerName = toHandlerKey(event))) || // also try camelCase event handler (#2249) - props[(handlerName = toHandlerKey(camelize(event)))] + getter(props, (handlerName = toHandlerKey(camelize(event)))) // for v-model update:xxx events, also trigger kebab-case equivalent // for props passed via kebab-case if (!handler && isModelListener) { - handler = props[(handlerName = toHandlerKey(hyphenate(event)))] + handler = getter(props, (handlerName = toHandlerKey(hyphenate(event)))) } if (handler) { callWithAsyncErrorHandling( - handler, + handler as Function, instance, ErrorCodes.COMPONENT_EVENT_HANDLER, args, @@ -222,12 +244,20 @@ export function emit( ) } - if (__COMPAT__) { - compatModelEmit(instance, event, args) - return compatInstanceEmit(instance, event, args) + if (__COMPAT__ && args) { + compatModelEmit(instance as ComponentInternalInstance, event, args) + return compatInstanceEmit( + instance as ComponentInternalInstance, + event, + args, + ) } } +function defaultPropGetter(props: Record, key: string): unknown { + return props[key] +} + export function normalizeEmitsOptions( comp: ConcreteComponent, appContext: AppContext, diff --git a/packages/runtime-core/src/devtools.ts b/packages/runtime-core/src/devtools.ts index 9ac4c433ac..8b300f6955 100644 --- a/packages/runtime-core/src/devtools.ts +++ b/packages/runtime-core/src/devtools.ts @@ -1,7 +1,7 @@ /* eslint-disable no-restricted-globals */ import type { App } from './apiCreateApp' import { Comment, Fragment, Static, Text } from './vnode' -import type { ComponentInternalInstance } from './component' +import type { GenericComponentInstance } from './component' interface AppRecord { id: number @@ -111,7 +111,7 @@ const _devtoolsComponentRemoved = /*@__PURE__*/ createDevtoolsComponentHook( ) export const devtoolsComponentRemoved = ( - component: ComponentInternalInstance, + component: GenericComponentInstance, ): void => { if ( devtools && @@ -123,13 +123,13 @@ export const devtoolsComponentRemoved = ( } } -type DevtoolsComponentHook = (component: ComponentInternalInstance) => void +type DevtoolsComponentHook = (component: GenericComponentInstance) => void /*! #__NO_SIDE_EFFECTS__ */ function createDevtoolsComponentHook( hook: DevtoolsHooks, ): DevtoolsComponentHook { - return (component: ComponentInternalInstance) => { + return (component: GenericComponentInstance) => { emit( hook, component.appContext.app, @@ -147,20 +147,20 @@ export const devtoolsPerfEnd: DevtoolsPerformanceHook = /*@__PURE__*/ createDevtoolsPerformanceHook(DevtoolsHooks.PERFORMANCE_END) type DevtoolsPerformanceHook = ( - component: ComponentInternalInstance, + component: GenericComponentInstance, type: string, time: number, ) => void function createDevtoolsPerformanceHook( hook: DevtoolsHooks, ): DevtoolsPerformanceHook { - return (component: ComponentInternalInstance, type: string, time: number) => { + return (component: GenericComponentInstance, type: string, time: number) => { emit(hook, component.appContext.app, component.uid, component, type, time) } } export function devtoolsComponentEmit( - component: ComponentInternalInstance, + component: GenericComponentInstance, event: string, params: any[], ): void { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index fc3ade2730..53b04b3f3c 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -491,7 +491,7 @@ export { baseNormalizePropsOptions, resolvePropValue, } from './componentProps' -export { isEmitListener } from './componentEmits' +export { baseEmit, isEmitListener } from './componentEmits' export { type SchedulerJob, queueJob } from './scheduler' export { type ComponentInternalOptions, diff --git a/packages/runtime-vapor/src/_new/component.ts b/packages/runtime-vapor/src/_new/component.ts index 955caf75ac..c91e54586c 100644 --- a/packages/runtime-vapor/src/_new/component.ts +++ b/packages/runtime-vapor/src/_new/component.ts @@ -57,7 +57,7 @@ interface SharedInternalOptions { /** * Cached normalized props proxy handlers. */ - __propsHandlers?: [ProxyHandler, ProxyHandler] + __propsHandlers?: [ProxyHandler | null, ProxyHandler] /** * Cached normalized emits options. */ @@ -124,6 +124,7 @@ export class VaporComponentInstance implements GenericComponentInstance { block: Block scope: EffectScope + rawProps: RawProps | undefined props: Record attrs: Record exposed?: Record @@ -138,29 +139,38 @@ export class VaporComponentInstance implements GenericComponentInstance { hasFallthrough: boolean + isMounted: boolean + isUnmounted: boolean + isDeactivated: boolean // LifecycleHooks.ERROR_CAPTURED ec: LifecycleHook + // dev only + propsOptions?: NormalizedPropsOptions + emitsOptions?: ObjectEmitsOptions | null + constructor(comp: VaporComponent, rawProps?: RawProps) { this.uid = nextUid() this.type = comp this.parent = currentInstance - this.appContext = currentInstance ? currentInstance.appContext : null! // TODO + // @ts-expect-error TODO use proper appContext + this.appContext = currentInstance ? currentInstance.appContext : {} this.block = null! // to be set this.scope = new EffectScope(true) + this.rawProps = rawProps this.provides = this.refs = EMPTY_OBJ - this.emitted = null - this.ec = null + this.emitted = this.ec = null + this.isMounted = this.isUnmounted = this.isDeactivated = false // init props this.propsDefaults = null this.hasFallthrough = false - if (comp.props && rawProps && rawProps.$) { + if (rawProps && rawProps.$) { // has dynamic props, use proxy const handlers = getDynamicPropsHandlers(comp, this) - this.props = new Proxy(rawProps, handlers[0]) + this.props = comp.props ? new Proxy(rawProps, handlers[0]!) : EMPTY_OBJ this.attrs = new Proxy(rawProps, handlers[1]) this.hasFallthrough = true } else { diff --git a/packages/runtime-vapor/src/_new/componentEmits.ts b/packages/runtime-vapor/src/_new/componentEmits.ts index 77c534f733..1a4ebbc511 100644 --- a/packages/runtime-vapor/src/_new/componentEmits.ts +++ b/packages/runtime-vapor/src/_new/componentEmits.ts @@ -1,10 +1,15 @@ -import type { EmitFn, ObjectEmitsOptions } from '@vue/runtime-core' +import { + type EmitFn, + type ObjectEmitsOptions, + baseEmit, +} from '@vue/runtime-core' import { type VaporComponent, type VaporComponentInstance, currentInstance, } from './component' -import { NOOP, isArray } from '@vue/shared' +import { NOOP, hasOwn, isArray } from '@vue/shared' +import { resolveSource } from './componentProps' /** * The logic from core isn't too reusable so it's better to duplicate here @@ -43,5 +48,19 @@ export function emit( event: string, ...rawArgs: any[] ): void { - // TODO extract reusable logic from core + const rawProps = instance.rawProps + if (!rawProps || instance.isUnmounted) return + baseEmit(instance, rawProps, propGetter, event, ...rawArgs) +} + +function propGetter(rawProps: Record, key: string) { + const dynamicSources = rawProps.$ + if (dynamicSources) { + let i = dynamicSources.length + while (i--) { + const source = resolveSource(dynamicSources[i]) + if (hasOwn(source, key)) return source[key] + } + } + return rawProps[key] && rawProps[key]() } diff --git a/packages/runtime-vapor/src/_new/componentProps.ts b/packages/runtime-vapor/src/_new/componentProps.ts index fb481bb4ed..560f89240b 100644 --- a/packages/runtime-vapor/src/_new/componentProps.ts +++ b/packages/runtime-vapor/src/_new/componentProps.ts @@ -26,6 +26,13 @@ export function initStaticProps( const { props, attrs } = instance const [propsOptions, needCastKeys] = normalizePropsOptions(comp) const emitsOptions = normalizeEmitsOptions(comp) + + // for dev emit check + if (__DEV__) { + instance.propsOptions = normalizePropsOptions(comp) + instance.emitsOptions = emitsOptions + } + for (const key in rawProps) { const normalizedKey = camelize(key) const needCast = needCastKeys && needCastKeys.includes(normalizedKey) @@ -91,21 +98,23 @@ function resolveDefault( } // TODO optimization: maybe convert functions into computeds -function resolveSource(source: PropSource): Record { +export function resolveSource(source: PropSource): Record { return isFunction(source) ? source() : source } +const passThrough = (val: any) => val + export function getDynamicPropsHandlers( comp: VaporComponent, instance: VaporComponentInstance, -): [ProxyHandler, ProxyHandler] { +): [ProxyHandler | null, ProxyHandler] { if (comp.__propsHandlers) { return comp.__propsHandlers } let normalizedKeys: string[] | undefined - const propsOptions = normalizePropsOptions(comp)[0]! + const propsOptions = normalizePropsOptions(comp)[0] const emitsOptions = normalizeEmitsOptions(comp) - const isProp = (key: string) => hasOwn(propsOptions, key) + const isProp = propsOptions ? (key: string) => hasOwn(propsOptions, key) : NO const getProp = (target: RawProps, key: string, asProp: boolean) => { if (key === '$') return @@ -114,17 +123,19 @@ export function getDynamicPropsHandlers( } else if (isProp(key) || isEmitListener(emitsOptions, key)) { return } - const castProp = (value: any, isAbsent = false) => - asProp - ? resolvePropValue( - propsOptions, - key as string, - value, - instance, - resolveDefault, - isAbsent, - ) - : value + const castProp = propsOptions + ? (value: any, isAbsent = false) => + asProp + ? resolvePropValue( + propsOptions, + key as string, + value, + instance, + resolveDefault, + isAbsent, + ) + : value + : passThrough if (key in target) { return castProp(resolveSource(target[key as string])) @@ -142,28 +153,29 @@ export function getDynamicPropsHandlers( return castProp(undefined, true) } - const propsHandlers = { - get: (target, key: string) => getProp(target, key, true), - has: (_, key: string) => isProp(key), - getOwnPropertyDescriptor(target, key: string) { - if (isProp(key)) { - return { - configurable: true, - enumerable: true, - get: () => getProp(target, key, true), - } - } - }, - ownKeys: () => - normalizedKeys || (normalizedKeys = Object.keys(propsOptions)), - set: NO, - deleteProperty: NO, - } satisfies ProxyHandler + const propsHandlers = propsOptions + ? ({ + get: (target, key: string) => getProp(target, key, true), + has: (_, key: string) => isProp(key), + getOwnPropertyDescriptor(target, key: string) { + if (isProp(key)) { + return { + configurable: true, + enumerable: true, + get: () => getProp(target, key, true), + } + } + }, + ownKeys: () => + normalizedKeys || (normalizedKeys = Object.keys(propsOptions)), + set: NO, + deleteProperty: NO, + } satisfies ProxyHandler) + : null const hasAttr = (target: RawProps, key: string) => { if (key === '$' || isProp(key) || isEmitListener(emitsOptions, key)) return false - if (hasOwn(target, key)) return true if (target.$) { let i = target.$.length while (i--) { @@ -172,7 +184,7 @@ export function getDynamicPropsHandlers( } } } - return false + return hasOwn(target, key) } const attrsHandlers = { @@ -188,14 +200,14 @@ export function getDynamicPropsHandlers( } }, ownKeys(target) { - const staticKeys = Object.keys(target) + const keys = Object.keys(target) if (target.$) { let i = target.$.length while (i--) { - staticKeys.push(...Object.keys(resolveSource(target.$[i]))) + keys.push(...Object.keys(resolveSource(target.$[i]))) } } - return staticKeys.filter(key => hasAttr(target, key)) + return keys.filter(key => hasAttr(target, key)) }, set: NO, deleteProperty: NO, diff --git a/packages/runtime-vapor/src/_new/index.ts b/packages/runtime-vapor/src/_new/index.ts index ed984366aa..886d1e2c50 100644 --- a/packages/runtime-vapor/src/_new/index.ts +++ b/packages/runtime-vapor/src/_new/index.ts @@ -1,3 +1,4 @@ export { createComponent as createComponentSimple } from './component' export { renderEffect as renderEffectSimple } from './renderEffect' export { createVaporApp as createVaporAppSimple } from './apiCreateApp' +export { useEmit } from './componentEmits'