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 { isArray, isFunction, isObject, isPromise } from '@vue/shared'
import { defineAsyncComponent } from '../apiAsyncComponent' 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 { isVNode } from '../vnode'
import { softAssertCompatEnabled } from './compatConfig' import { isCompatEnabled, softAssertCompatEnabled } from './compatConfig'
import { DeprecationTypes } from './deprecations' import { DeprecationTypes, warnDeprecation } from './deprecations'
import { getCompatListeners } from './instanceListeners'
import { compatH } from './renderFn'
export function convertLegacyComponent(comp: any): Component { export function convertLegacyComponent(comp: any): Component {
// 2.x async component // 2.x async component
if ( // since after disabling this, plain functions are still valid usage, do not
isFunction(comp) && // use softAssert here.
softAssertCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, comp) if (isFunction(comp) && isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC)) {
) { __DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, comp)
return convertLegacyAsyncComponent(comp) return convertLegacyAsyncComponent(comp)
} }
@ -78,6 +87,56 @@ function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
return converted return converted
} }
function convertLegacyFunctionalComponent(comp: ComponentOptions) { const normalizedFunctionalComponentMap = new Map<
return comp.render as FunctionalComponent 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` name ? ` <${name}>` : `s`
} should be explicitly created via \`defineAsyncComponent()\` ` + } should be explicitly created via \`defineAsyncComponent()\` ` +
`in Vue 3. Plain functions will be treated as functional components in ` + `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` link: `https://v3.vuejs.org/guide/migration/async-components.html`
@ -327,13 +333,10 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
`Functional component${ `Functional component${
name ? ` <${name}>` : `s` name ? ` <${name}>` : `s`
} should be defined as a plain function in Vue 3. The "functional" ` + } should be defined as a plain function in Vue 3. The "functional" ` +
`option has been removed.\n` + `option has been removed. NOTE: Before migrating to use plain ` +
`NOTE: Before migrating, ensure that all async ` + `functions for functional components, first make sure that all async ` +
`components have been upgraded to use \`defineAsyncComponent()\` and ` + `components usage have been migrated and its compat behavior has ` +
`then disable compat for legacy async components with:` + `been disabled.`
`\n\n configureCompat({ ${
DeprecationTypes.COMPONENT_ASYNC
}: false })\n`
) )
}, },
link: `https://v3.vuejs.org/guide/migration/functional-components.html` link: `https://v3.vuejs.org/guide/migration/functional-components.html`

View File

@ -41,17 +41,21 @@ type LegacyVNodeChildren =
| VNode | VNode
| VNodeArrayChildren | VNodeArrayChildren
export function h( export function compatH(
type: string | Component, type: string | Component,
children?: LegacyVNodeChildren children?: LegacyVNodeChildren
): VNode ): VNode
export function h( export function compatH(
type: string | Component, type: string | Component,
props?: LegacyVNodeProps, props?: LegacyVNodeProps,
children?: LegacyVNodeChildren children?: LegacyVNodeChildren
): VNode ): 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 const l = arguments.length
if (l === 2) { if (l === 2) {
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) { 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 { function convertLegacyProps(props: LegacyVNodeProps): Data & VNodeProps {
// TODO // TODO
return {} return props as any
} }
function convertLegacyDirectives(vnode: VNode, props: LegacyVNodeProps): VNode { function convertLegacyDirectives(vnode: VNode, props: LegacyVNodeProps): VNode {

View File

@ -597,31 +597,7 @@ export function applyOptions(
// - watch (deferred since it relies on `this` access) // - watch (deferred since it relies on `this` access)
if (injectOptions) { if (injectOptions) {
if (isArray(injectOptions)) { resolveInjections(injectOptions, ctx, checkDuplicateProperties)
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)
}
}
}
} }
if (methods) { 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( function callSyncHook(
name: 'beforeCreate' | 'created', name: 'beforeCreate' | 'created',
type: LifecycleHooks, type: LifecycleHooks,