]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: emits
authorEvan You <evan@vuejs.org>
Tue, 3 Dec 2024 14:49:28 +0000 (22:49 +0800)
committerEvan You <evan@vuejs.org>
Tue, 3 Dec 2024 14:49:28 +0000 (22:49 +0800)
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentEmits.ts
packages/runtime-core/src/devtools.ts
packages/runtime-core/src/index.ts
packages/runtime-vapor/src/_new/component.ts
packages/runtime-vapor/src/_new/componentEmits.ts
packages/runtime-vapor/src/_new/componentProps.ts
packages/runtime-vapor/src/_new/index.ts

index ca6419dce3ee8af218e360d595a755658741f60c..b5cad12eab4d0a34d9d9766a055abfb3ad7e901c 100644 (file)
@@ -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
    */
index abeb74a6d466998804c344a469004316ffdd6c4f..6ce9f371a04196e1ef24e97ede4208b830cc3538 100644 (file)
@@ -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<string, any>,
+  getter: (props: Record<string, any>, 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<string, any>, key: string): unknown {
+  return props[key]
+}
+
 export function normalizeEmitsOptions(
   comp: ConcreteComponent,
   appContext: AppContext,
index 9ac4c433ac162b16b5de10b09a52c3e16d2b43d7..8b300f6955b3fdd67f717e52ace26b07c7655f60 100644 (file)
@@ -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 {
index fc3ade2730a0a8b2a2fdecd1a23d411db47d34da..53b04b3f3c77ae2f18849e13e403123f9815afc3 100644 (file)
@@ -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,
index 955caf75ac95ecf10a7695c635e1d1d888ace849..c91e54586cf863b3dec3a9a20c687599aa9b82a8 100644 (file)
@@ -57,7 +57,7 @@ interface SharedInternalOptions {
   /**
    * Cached normalized props proxy handlers.
    */
-  __propsHandlers?: [ProxyHandler<any>, ProxyHandler<any>]
+  __propsHandlers?: [ProxyHandler<any> | null, ProxyHandler<any>]
   /**
    * Cached normalized emits options.
    */
@@ -124,6 +124,7 @@ export class VaporComponentInstance implements GenericComponentInstance {
 
   block: Block
   scope: EffectScope
+  rawProps: RawProps | undefined
   props: Record<string, any>
   attrs: Record<string, any>
   exposed?: Record<string, any>
@@ -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 {
index 77c534f733c0cd5a4e9cdc537e512ec191c38128..1a4ebbc5118ab50a615d19f72d88452d5e60f185 100644 (file)
@@ -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<string, any>, 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]()
 }
index fb481bb4edffd6aaaac80b0e6ee2b37f0db6440d..560f89240b6c2a78aa8bb8b1edb46181de826817 100644 (file)
@@ -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<string, any> {
+export function resolveSource(source: PropSource): Record<string, any> {
   return isFunction(source) ? source() : source
 }
 
+const passThrough = (val: any) => val
+
 export function getDynamicPropsHandlers(
   comp: VaporComponent,
   instance: VaporComponentInstance,
-): [ProxyHandler<RawProps>, ProxyHandler<RawProps>] {
+): [ProxyHandler<RawProps> | null, ProxyHandler<RawProps>] {
   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<RawProps>
+  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<RawProps>)
+    : 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,
index ed984366aa48fa036ea69016ebe939285b69ec91..886d1e2c5058519beef4a228fb01cda59d79e2e5 100644 (file)
@@ -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'