wip: compat for legacy functional component

This commit is contained in:
Evan You 2021-04-09 15:14:14 -04:00
parent d71c488540
commit 457a56e331
4 changed files with 120 additions and 46 deletions

View 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
}
function convertLegacyFunctionalComponent(comp: ComponentOptions) {
return comp.render as FunctionalComponent
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) {
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
}

View 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`

View 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 {

View 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,