]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: instance event emitter api compat
authorEvan You <yyx990803@gmail.com>
Tue, 6 Apr 2021 19:58:12 +0000 (15:58 -0400)
committerEvan You <yyx990803@gmail.com>
Wed, 7 Apr 2021 20:19:24 +0000 (16:19 -0400)
packages/runtime-core/src/compat/deprecations.ts
packages/runtime-core/src/compat/eventEmitter.ts [new file with mode: 0644]
packages/runtime-core/src/compat/instance.ts
packages/runtime-core/src/componentEmits.ts
packages/runtime-core/src/renderer.ts

index 6cadda92452d812a1279730e5fa1135303960c43..f71d77ff040c07923178381fa34878fe71898961 100644 (file)
@@ -18,6 +18,8 @@ export const enum DeprecationTypes {
   INSTANCE_DELETE,
   INSTANCE_MOUNT,
   INSTANCE_DESTROY,
+  INSTANCE_EVENT_EMITTER,
+  INSTANCE_EVENT_HOOKS,
 
   OPTIONS_DATA_FN,
   OPTIONS_DATA_MERGE,
@@ -132,6 +134,20 @@ const deprecations: Record<DeprecationTypes, DeprecationData> = {
     link: `https://v3.vuejs.org/api/application-api.html#unmount`
   },
 
+  [DeprecationTypes.INSTANCE_EVENT_EMITTER]: {
+    message:
+      `vm.$on/$once/$off() have been removed. ` +
+      `Use an external event emitter library instead.`,
+    link: `https://v3.vuejs.org/guide/migration/events-api.html`
+  },
+
+  [DeprecationTypes.INSTANCE_EVENT_HOOKS]: {
+    message:
+      `"hook:x" lifecycle events are no longer supported. ` +
+      `Use Composition API to dynamically register lifecycle hooks.`,
+    link: `https://v3.vuejs.org/api/composition-api.html#lifecycle-hooks`
+  },
+
   [DeprecationTypes.OPTIONS_DATA_FN]: {
     message:
       `The "data" option can no longer be a plain object. ` +
@@ -169,10 +185,17 @@ const deprecations: Record<DeprecationTypes, DeprecationData> = {
   }
 }
 
+const hasWarned: Record<string, boolean> = {}
+
 export function warnDeprecation(key: DeprecationTypes, ...args: any[]) {
   if (!__COMPAT__ || !__DEV__) {
     return
   }
+  const dupKey = key + args.join('')
+  if (hasWarned[dupKey]) {
+    return
+  }
+  hasWarned[dupKey] = true
   const { message, link } = deprecations[key]
   warn(
     `[DEPRECATION] ${
diff --git a/packages/runtime-core/src/compat/eventEmitter.ts b/packages/runtime-core/src/compat/eventEmitter.ts
new file mode 100644 (file)
index 0000000..ca89e04
--- /dev/null
@@ -0,0 +1,106 @@
+import { isArray } from '@vue/shared'
+import { ComponentInternalInstance } from '../component'
+import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling'
+import { DeprecationTypes, warnDeprecation } from './deprecations'
+
+interface EventRegistry {
+  [event: string]: Function[] | undefined
+}
+
+const eventRegistryMap = /*#__PURE__*/ new WeakMap<
+  ComponentInternalInstance,
+  EventRegistry
+>()
+
+export function getRegistry(
+  instance: ComponentInternalInstance
+): EventRegistry {
+  let events = eventRegistryMap.get(instance)
+  if (!events) {
+    eventRegistryMap.set(instance, (events = Object.create(null)))
+  }
+  return events!
+}
+
+export function on(
+  instance: ComponentInternalInstance,
+  event: string | string[],
+  fn: Function
+) {
+  if (isArray(event)) {
+    event.forEach(e => on(instance, e, fn))
+  } else {
+    const events = getRegistry(instance)
+    ;(events[event] || (events[event] = [])).push(fn)
+    if (__DEV__) {
+      if (event.startsWith('hook:')) {
+        warnDeprecation(DeprecationTypes.INSTANCE_EVENT_HOOKS)
+      } else {
+        warnDeprecation(DeprecationTypes.INSTANCE_EVENT_EMITTER)
+      }
+    }
+  }
+  return instance.proxy
+}
+
+export function once(
+  instance: ComponentInternalInstance,
+  event: string,
+  fn: Function
+) {
+  const wrapped = (...args: any[]) => {
+    off(instance, event, wrapped)
+    fn.call(instance.proxy, ...args)
+  }
+  wrapped.fn = fn
+  on(instance, event, wrapped)
+  return instance.proxy
+}
+
+export function off(
+  instance: ComponentInternalInstance,
+  event?: string,
+  fn?: Function
+) {
+  __DEV__ && warnDeprecation(DeprecationTypes.INSTANCE_EVENT_EMITTER)
+  const vm = instance.proxy
+  // all
+  if (!arguments.length) {
+    eventRegistryMap.set(instance, Object.create(null))
+    return vm
+  }
+  // array of events
+  if (isArray(event)) {
+    event.forEach(e => off(instance, e, fn))
+    return vm
+  }
+  // specific event
+  const events = getRegistry(instance)
+  const cbs = events[event!]
+  if (!cbs) {
+    return vm
+  }
+  if (!fn) {
+    events[event!] = undefined
+    return vm
+  }
+  events[event!] = cbs.filter(cb => !(cb === fn || (cb as any).fn === fn))
+  return vm
+}
+
+export function emit(
+  instance: ComponentInternalInstance,
+  event: string,
+  ...args: any[]
+) {
+  const cbs = getRegistry(instance)[event]
+  if (cbs) {
+    callWithAsyncErrorHandling(
+      cbs,
+      instance,
+      ErrorCodes.COMPONENT_EVENT_HANDLER,
+      args
+    )
+  }
+  return instance.proxy
+}
index b92a1ebe10576eb4e75ca3fdeea435e5320a0d0d..a7158addbfb804fe6f84f88aee8b9d2353eb07a9 100644 (file)
@@ -1,6 +1,7 @@
 import { extend, NOOP } from '@vue/shared'
 import { PublicPropertiesMap } from '../componentPublicInstance'
 import { DeprecationTypes, warnDeprecation } from './deprecations'
+import { off, on, once } from './eventEmitter'
 
 export function installCompatInstanceProperties(map: PublicPropertiesMap) {
   const set = (target: any, key: any, val: any) => {
@@ -29,6 +30,9 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
       __DEV__ && warnDeprecation(DeprecationTypes.INSTANCE_DESTROY)
       // root destroy override from ./global.ts in installCompatMount
       return i.ctx._compat_destroy || NOOP
-    }
+    },
+    $on: i => on.bind(null, i),
+    $once: i => once.bind(null, i),
+    $off: i => off.bind(null, i)
   } as PublicPropertiesMap)
 }
index ab382edc80c53c3971b6e509e79cca890b119c17..f40e67abc6c4831adb6982f9807a79d3176c9d00 100644 (file)
@@ -21,6 +21,7 @@ import { warn } from './warning'
 import { UnionToIntersection } from './helpers/typeUtils'
 import { devtoolsComponentEmit } from './devtools'
 import { AppContext } from './apiCreateApp'
+import { emit as compatEmit } from './compat/eventEmitter'
 
 export type ObjectEmitsOptions = Record<
   string,
@@ -148,6 +149,10 @@ export function emit(
       args
     )
   }
+
+  if (__COMPAT__) {
+    return compatEmit(instance, event, args)
+  }
 }
 
 export function normalizeEmitsOptions(
index ea065d580b51aa9e63badd4c2c6c62411e885dee..07384780e7f34f168252741776665db5e68c6ea5 100644 (file)
@@ -1417,6 +1417,9 @@ function baseCreateRenderer(
         if ((vnodeHook = props && props.onVnodeBeforeMount)) {
           invokeVNodeHook(vnodeHook, parent, initialVNode)
         }
+        if (__COMPAT__) {
+          instance.emit('hook:beforeMount')
+        }
 
         // render
         if (__DEV__) {
@@ -1467,19 +1470,29 @@ function baseCreateRenderer(
         // onVnodeMounted
         if ((vnodeHook = props && props.onVnodeMounted)) {
           const scopedInitialVNode = initialVNode
-          queuePostRenderEffect(() => {
-            invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode)
-          }, parentSuspense)
+          queuePostRenderEffect(
+            () => invokeVNodeHook(vnodeHook!, parent, scopedInitialVNode),
+            parentSuspense
+          )
         }
+        if (__COMPAT__) {
+          queuePostRenderEffect(
+            () => instance.emit('hook:mounted'),
+            parentSuspense
+          )
+        }
+
         // activated hook for keep-alive roots.
         // #1742 activated hook must be accessed after first render
         // since the hook may be injected by a child keep-alive
-        const { a } = instance
-        if (
-          a &&
-          initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
-        ) {
-          queuePostRenderEffect(a, parentSuspense)
+        if (initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
+          instance.a && queuePostRenderEffect(instance.a, parentSuspense)
+          if (__COMPAT__) {
+            queuePostRenderEffect(
+              () => instance.emit('hook:activated'),
+              parentSuspense
+            )
+          }
         }
         instance.isMounted = true
 
@@ -1515,6 +1528,9 @@ function baseCreateRenderer(
         if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
           invokeVNodeHook(vnodeHook, parent, next, vnode)
         }
+        if (__COMPAT__) {
+          instance.emit('hook:beforeUpdate')
+        }
 
         // render
         if (__DEV__) {
@@ -1557,9 +1573,16 @@ function baseCreateRenderer(
         }
         // onVnodeUpdated
         if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
-          queuePostRenderEffect(() => {
-            invokeVNodeHook(vnodeHook!, parent, next!, vnode)
-          }, parentSuspense)
+          queuePostRenderEffect(
+            () => invokeVNodeHook(vnodeHook!, parent, next!, vnode),
+            parentSuspense
+          )
+        }
+        if (__COMPAT__) {
+          queuePostRenderEffect(
+            () => instance.emit('hook:updated'),
+            parentSuspense
+          )
         }
 
         if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
@@ -2211,10 +2234,15 @@ function baseCreateRenderer(
     }
 
     const { bum, effects, update, subTree, um } = instance
+
     // beforeUnmount hook
     if (bum) {
       invokeArrayFns(bum)
     }
+    if (__COMPAT__) {
+      instance.emit('hook:beforeDestroy')
+    }
+
     if (effects) {
       for (let i = 0; i < effects.length; i++) {
         stop(effects[i])
@@ -2230,6 +2258,12 @@ function baseCreateRenderer(
     if (um) {
       queuePostRenderEffect(um, parentSuspense)
     }
+    if (__COMPAT__) {
+      queuePostRenderEffect(
+        () => instance.emit('hook:destroyed'),
+        parentSuspense
+      )
+    }
     queuePostRenderEffect(() => {
       instance.isUnmounted = true
     }, parentSuspense)