Skip to content

Commit 457a56e

Browse files
committed
wip: compat for legacy functional component
1 parent d71c488 commit 457a56e

File tree

4 files changed

+119
-45
lines changed

4 files changed

+119
-45
lines changed

packages/runtime-core/src/compat/component.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
import { isArray, isFunction, isObject, isPromise } from '@vue/shared'
22
import { defineAsyncComponent } from '../apiAsyncComponent'
3-
import { Component, ComponentOptions, FunctionalComponent } from '../component'
3+
import {
4+
Component,
5+
ComponentOptions,
6+
FunctionalComponent,
7+
getCurrentInstance
8+
} from '../component'
9+
import { resolveInjections } from '../componentOptions'
10+
import { InternalSlots } from '../componentSlots'
411
import { isVNode } from '../vnode'
5-
import { softAssertCompatEnabled } from './compatConfig'
6-
import { DeprecationTypes } from './deprecations'
12+
import { isCompatEnabled, softAssertCompatEnabled } from './compatConfig'
13+
import { DeprecationTypes, warnDeprecation } from './deprecations'
14+
import { getCompatListeners } from './instanceListeners'
15+
import { compatH } from './renderFn'
716

817
export function convertLegacyComponent(comp: any): Component {
918
// 2.x async component
10-
if (
11-
isFunction(comp) &&
12-
softAssertCompatEnabled(DeprecationTypes.COMPONENT_ASYNC, comp)
13-
) {
19+
// since after disabling this, plain functions are still valid usage, do not
20+
// use softAssert here.
21+
if (isFunction(comp) && isCompatEnabled(DeprecationTypes.COMPONENT_ASYNC)) {
22+
__DEV__ && warnDeprecation(DeprecationTypes.COMPONENT_ASYNC, comp)
1423
return convertLegacyAsyncComponent(comp)
1524
}
1625

@@ -78,6 +87,56 @@ function convertLegacyAsyncComponent(comp: LegacyAsyncComponent) {
7887
return converted
7988
}
8089

90+
const normalizedFunctionalComponentMap = new Map<
91+
ComponentOptions,
92+
FunctionalComponent
93+
>()
94+
95+
const legacySlotProxyHandlers: ProxyHandler<InternalSlots> = {
96+
get(target, key: string) {
97+
const slot = target[key]
98+
return slot && slot()
99+
}
100+
}
101+
81102
function convertLegacyFunctionalComponent(comp: ComponentOptions) {
82-
return comp.render as FunctionalComponent
103+
if (normalizedFunctionalComponentMap.has(comp)) {
104+
return normalizedFunctionalComponentMap.get(comp)!
105+
}
106+
107+
const legacyFn = comp.render as any
108+
109+
const Func: FunctionalComponent = (props, ctx) => {
110+
const instance = getCurrentInstance()!
111+
112+
const legacyCtx = {
113+
props,
114+
children: instance.vnode.children || [],
115+
data: instance.vnode.props || {},
116+
scopedSlots: ctx.slots,
117+
parent: instance.parent && instance.parent.proxy,
118+
get slots() {
119+
return new Proxy(ctx.slots, legacySlotProxyHandlers)
120+
},
121+
get listeners() {
122+
return getCompatListeners(instance)
123+
},
124+
get injections() {
125+
if (comp.inject) {
126+
const injections = {}
127+
resolveInjections(comp.inject, {})
128+
return injections
129+
}
130+
return {}
131+
}
132+
}
133+
return legacyFn(compatH, legacyCtx)
134+
}
135+
Func.props = comp.props
136+
Func.displayName = comp.name
137+
// v2 functional components do not inherit attrs
138+
Func.inheritAttrs = false
139+
140+
normalizedFunctionalComponentMap.set(comp, Func)
141+
return Func
83142
}

packages/runtime-core/src/compat/deprecations.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,13 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
314314
name ? ` <${name}>` : `s`
315315
} should be explicitly created via \`defineAsyncComponent()\` ` +
316316
`in Vue 3. Plain functions will be treated as functional components in ` +
317-
`non-compat build.`
317+
`non-compat build. If you have already migrated all async component ` +
318+
`usage and intend to use plain functions for functional components, ` +
319+
`you can disable the compat behavior and suppress this ` +
320+
`warning with:` +
321+
`\n\n configureCompat({ ${
322+
DeprecationTypes.COMPONENT_ASYNC
323+
}: false })\n`
318324
)
319325
},
320326
link: `https://v3.vuejs.org/guide/migration/async-components.html`
@@ -327,13 +333,10 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
327333
`Functional component${
328334
name ? ` <${name}>` : `s`
329335
} should be defined as a plain function in Vue 3. The "functional" ` +
330-
`option has been removed.\n` +
331-
`NOTE: Before migrating, ensure that all async ` +
332-
`components have been upgraded to use \`defineAsyncComponent()\` and ` +
333-
`then disable compat for legacy async components with:` +
334-
`\n\n configureCompat({ ${
335-
DeprecationTypes.COMPONENT_ASYNC
336-
}: false })\n`
336+
`option has been removed. NOTE: Before migrating to use plain ` +
337+
`functions for functional components, first make sure that all async ` +
338+
`components usage have been migrated and its compat behavior has ` +
339+
`been disabled.`
337340
)
338341
},
339342
link: `https://v3.vuejs.org/guide/migration/functional-components.html`

packages/runtime-core/src/compat/renderFn.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,21 @@ type LegacyVNodeChildren =
4141
| VNode
4242
| VNodeArrayChildren
4343

44-
export function h(
44+
export function compatH(
4545
type: string | Component,
4646
children?: LegacyVNodeChildren
4747
): VNode
48-
export function h(
48+
export function compatH(
4949
type: string | Component,
5050
props?: LegacyVNodeProps,
5151
children?: LegacyVNodeChildren
5252
): VNode
5353

54-
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
54+
export function compatH(
55+
type: any,
56+
propsOrChildren?: any,
57+
children?: any
58+
): VNode {
5559
const l = arguments.length
5660
if (l === 2) {
5761
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
@@ -85,7 +89,7 @@ export function h(type: any, propsOrChildren?: any, children?: any): VNode {
8589

8690
function convertLegacyProps(props: LegacyVNodeProps): Data & VNodeProps {
8791
// TODO
88-
return {}
92+
return props as any
8993
}
9094

9195
function convertLegacyDirectives(vnode: VNode, props: LegacyVNodeProps): VNode {

packages/runtime-core/src/componentOptions.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -597,31 +597,7 @@ export function applyOptions(
597597
// - watch (deferred since it relies on `this` access)
598598

599599
if (injectOptions) {
600-
if (isArray(injectOptions)) {
601-
for (let i = 0; i < injectOptions.length; i++) {
602-
const key = injectOptions[i]
603-
ctx[key] = inject(key)
604-
if (__DEV__) {
605-
checkDuplicateProperties!(OptionTypes.INJECT, key)
606-
}
607-
}
608-
} else {
609-
for (const key in injectOptions) {
610-
const opt = injectOptions[key]
611-
if (isObject(opt)) {
612-
ctx[key] = inject(
613-
opt.from || key,
614-
opt.default,
615-
true /* treat default function as factory */
616-
)
617-
} else {
618-
ctx[key] = inject(opt)
619-
}
620-
if (__DEV__) {
621-
checkDuplicateProperties!(OptionTypes.INJECT, key)
622-
}
623-
}
624-
}
600+
resolveInjections(injectOptions, ctx, checkDuplicateProperties)
625601
}
626602

627603
if (methods) {
@@ -842,6 +818,38 @@ export function applyOptions(
842818
}
843819
}
844820

821+
export function resolveInjections(
822+
injectOptions: ComponentInjectOptions,
823+
ctx: any,
824+
checkDuplicateProperties = NOOP as any
825+
) {
826+
if (isArray(injectOptions)) {
827+
for (let i = 0; i < injectOptions.length; i++) {
828+
const key = injectOptions[i]
829+
ctx[key] = inject(key)
830+
if (__DEV__) {
831+
checkDuplicateProperties!(OptionTypes.INJECT, key)
832+
}
833+
}
834+
} else {
835+
for (const key in injectOptions) {
836+
const opt = injectOptions[key]
837+
if (isObject(opt)) {
838+
ctx[key] = inject(
839+
opt.from || key,
840+
opt.default,
841+
true /* treat default function as factory */
842+
)
843+
} else {
844+
ctx[key] = inject(opt)
845+
}
846+
if (__DEV__) {
847+
checkDuplicateProperties!(OptionTypes.INJECT, key)
848+
}
849+
}
850+
}
851+
}
852+
845853
function callSyncHook(
846854
name: 'beforeCreate' | 'created',
847855
type: LifecycleHooks,

0 commit comments

Comments
 (0)