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)
}
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
}
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`
`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`
| 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)) {
function convertLegacyProps(props: LegacyVNodeProps): Data & VNodeProps {
// TODO
- return {}
+ return props as any
}
function convertLegacyDirectives(vnode: VNode, props: LegacyVNodeProps): VNode {
// - 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) {
}
}
+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,