]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
wip: compat for legacy functional component
authorEvan You <yyx990803@gmail.com>
Fri, 9 Apr 2021 19:14:14 +0000 (15:14 -0400)
committerEvan You <yyx990803@gmail.com>
Fri, 9 Apr 2021 19:14:14 +0000 (15:14 -0400)
packages/runtime-core/src/compat/component.ts
packages/runtime-core/src/compat/deprecations.ts
packages/runtime-core/src/compat/renderFn.ts
packages/runtime-core/src/componentOptions.ts

index 6168c3e75fd21c3e047f73e4822fee0661087e51..c67c681f042992815c6cba900136ce8148224996 100644 (file)
@@ -1,16 +1,25 @@
 import { isArray, isFunction, isObject, isPromise } from '@vue/shared'
 import { defineAsyncComponent } from '../apiAsyncComponent'
-import { Component, ComponentOptions, FunctionalComponent } from '../component'
+import {
+  Component,
+  ComponentOptions,
+  FunctionalComponent,
+  getCurrentInstance
+} from '../component'
+import { resolveInjections } from '../componentOptions'
+import { InternalSlots } from '../componentSlots'
 import { isVNode } from '../vnode'
-import { softAssertCompatEnabled } from './compatConfig'
-import { DeprecationTypes } from './deprecations'
+import { isCompatEnabled, softAssertCompatEnabled } from './compatConfig'
+import { DeprecationTypes, warnDeprecation } from './deprecations'
+import { getCompatListeners } from './instanceListeners'
+import { compatH } from './renderFn'
 
 export function convertLegacyComponent(comp: any): Component {
   // 2.x async component
-  if (
-    isFunction(comp) &&
-    softAssertCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, comp)
-  ) {
+  // since after disabling this, plain functions are still valid usage, do not
+  // use softAssert here.
+  if (isFunction(comp) && isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC)) {
+    __DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, comp)
     return convertLegacyAsyncComponent(comp)
   }
 
@@ -78,6 +87,56 @@ function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
   return converted
 }
 
+const normalizedFunctionalComponentMap = new Map<
+  ComponentOptions,
+  FunctionalComponent
+>()
+
+const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
+  get(target, key: string) {
+    const slot = target[key]
+    return slot && slot()
+  }
+}
+
 function convertLegacyFunctionalComponent(comp: ComponentOptions) {
-  return comp.render as FunctionalComponent
+  if (normalizedFunctionalComponentMap.has(comp)) {
+    return normalizedFunctionalComponentMap.get(comp)!
+  }
+
+  const legacyFn = comp.render as any
+
+  const Func: FunctionalComponent = (props, ctx) => {
+    const instance = getCurrentInstance()!
+
+    const legacyCtx = {
+      props,
+      children: instance.vnode.children || [],
+      data: instance.vnode.props || {},
+      scopedSlots: ctx.slots,
+      parent: instance.parent && instance.parent.proxy,
+      get slots() {
+        return new Proxy(ctx.slots, legacySlotProxyHandlers)
+      },
+      get listeners() {
+        return getCompatListeners(instance)
+      },
+      get injections() {
+        if (comp.inject) {
+          const injections = {}
+          resolveInjections(comp.inject, {})
+          return injections
+        }
+        return {}
+      }
+    }
+    return legacyFn(compatH, legacyCtx)
+  }
+  Func.props = comp.props
+  Func.displayName = comp.name
+  // v2 functional components do not inherit attrs
+  Func.inheritAttrs = false
+
+  normalizedFunctionalComponentMap.set(comp, Func)
+  return Func
 }
index c2407af59f680dfc4183631f508b9a722dacace2..5894987684fa1cc9be91147df47a91084898fa95 100644 (file)
@@ -314,7 +314,13 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
           name ? ` <${name}>` : `s`
         } should be explicitly created via \`defineAsyncComponent()\` ` +
         `in Vue 3. Plain functions will be treated as functional components in ` +
-        `non-compat build.`
+        `non-compat build. If you have already migrated all async component ` +
+        `usage and intend to use plain functions for functional components, ` +
+        `you can disable the compat behavior and suppress this ` +
+        `warning with:` +
+        `\n\n  configureCompat({ ${
+          DeprecationTypes.COMPONENT_ASYNC
+        }: false })\n`
       )
     },
     link: `https://v3.vuejs.org/guide/migration/async-components.html`
@@ -327,13 +333,10 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
         `Functional component${
           name ? ` <${name}>` : `s`
         } should be defined as a plain function in Vue 3. The "functional" ` +
-        `option has been removed.\n` +
-        `NOTE: Before migrating, ensure that all async ` +
-        `components have been upgraded to use \`defineAsyncComponent()\` and ` +
-        `then disable compat for legacy async components with:` +
-        `\n\n  configureCompat({ ${
-          DeprecationTypes.COMPONENT_ASYNC
-        }: false })\n`
+        `option has been removed. NOTE: Before migrating to use plain ` +
+        `functions for functional components, first make sure that all async ` +
+        `components usage have been migrated and its compat behavior has ` +
+        `been disabled.`
       )
     },
     link: `https://v3.vuejs.org/guide/migration/functional-components.html`
index b9074af186c678c40f45a0e660543fce9538d474..ef4cd37b1e72b68a0ac9c913738169dd3a474942 100644 (file)
@@ -41,17 +41,21 @@ type LegacyVNodeChildren =
   | VNode
   | VNodeArrayChildren
 
-export function h(
+export function compatH(
   type: string | Component,
   children?: LegacyVNodeChildren
 ): VNode
-export function h(
+export function compatH(
   type: string | Component,
   props?: LegacyVNodeProps,
   children?: LegacyVNodeChildren
 ): VNode
 
-export function h(type: any, propsOrChildren?: any, children?: any): VNode {
+export function compatH(
+  type: any,
+  propsOrChildren?: any,
+  children?: any
+): VNode {
   const l = arguments.length
   if (l === 2) {
     if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
@@ -85,7 +89,7 @@ export function h(type: any, propsOrChildren?: any, children?: any): VNode {
 
 function convertLegacyProps(props: LegacyVNodeProps): Data & VNodeProps {
   // TODO
-  return {}
+  return props as any
 }
 
 function convertLegacyDirectives(vnode: VNode, props: LegacyVNodeProps): VNode {
index 4a93c697c344aa59d35e4666b88911d28212f296..1f182c6d5c2200aa77aa7a1cdaf113429d7dc70b 100644 (file)
@@ -597,31 +597,7 @@ export function applyOptions(
   // - watch (deferred since it relies on `this` access)
 
   if (injectOptions) {
-    if (isArray(injectOptions)) {
-      for (let i = 0; i < injectOptions.length; i++) {
-        const key = injectOptions[i]
-        ctx[key] = inject(key)
-        if (__DEV__) {
-          checkDuplicateProperties!(OptionTypes.INJECT, key)
-        }
-      }
-    } else {
-      for (const key in injectOptions) {
-        const opt = injectOptions[key]
-        if (isObject(opt)) {
-          ctx[key] = inject(
-            opt.from || key,
-            opt.default,
-            true /* treat default function as factory */
-          )
-        } else {
-          ctx[key] = inject(opt)
-        }
-        if (__DEV__) {
-          checkDuplicateProperties!(OptionTypes.INJECT, key)
-        }
-      }
-    }
+    resolveInjections(injectOptions, ctx, checkDuplicateProperties)
   }
 
   if (methods) {
@@ -842,6 +818,38 @@ export function applyOptions(
   }
 }
 
+export function resolveInjections(
+  injectOptions: ComponentInjectOptions,
+  ctx: any,
+  checkDuplicateProperties = NOOP as any
+) {
+  if (isArray(injectOptions)) {
+    for (let i = 0; i < injectOptions.length; i++) {
+      const key = injectOptions[i]
+      ctx[key] = inject(key)
+      if (__DEV__) {
+        checkDuplicateProperties!(OptionTypes.INJECT, key)
+      }
+    }
+  } else {
+    for (const key in injectOptions) {
+      const opt = injectOptions[key]
+      if (isObject(opt)) {
+        ctx[key] = inject(
+          opt.from || key,
+          opt.default,
+          true /* treat default function as factory */
+        )
+      } else {
+        ctx[key] = inject(opt)
+      }
+      if (__DEV__) {
+        checkDuplicateProperties!(OptionTypes.INJECT, key)
+      }
+    }
+  }
+}
+
 function callSyncHook(
   name: 'beforeCreate' | 'created',
   type: LifecycleHooks,