Skip to content

Commit 183f9b0

Browse files
committed
wip: component v-model compat
1 parent f05d6df commit 183f9b0

File tree

6 files changed

+120
-6
lines changed

6 files changed

+120
-6
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { hasOwn, isArray } from '@vue/shared/src'
12
import {
23
ComponentInternalInstance,
4+
ComponentOptions,
35
formatComponentName,
46
getComponentName,
57
getCurrentInstance,
@@ -52,6 +54,7 @@ export const enum DeprecationTypes {
5254

5355
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
5456
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
57+
COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
5558

5659
RENDER_FUNCTION = 'RENDER_FUNCTION'
5760
}
@@ -345,6 +348,32 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
345348
link: `https://v3.vuejs.org/guide/migration/functional-components.html`
346349
},
347350

351+
[DeprecationTypes.COMPONENT_V_MODEL]: {
352+
message: (comp: ComponentOptions) => {
353+
const configMsg =
354+
`opt-in to ` +
355+
`Vue 3 behavior on a per-component basis with \`compatConfig: { ${
356+
DeprecationTypes.COMPONENT_V_MODEL
357+
}: false }\`.`
358+
if (
359+
comp.props && isArray(comp.props)
360+
? comp.props.includes('modelValue')
361+
: hasOwn(comp.props, 'modelValue')
362+
) {
363+
return (
364+
`Component delcares "modelValue" prop, which is Vue 3 usage, but ` +
365+
`is running under Vue 2 compat v-model behavior. You can ${configMsg}`
366+
)
367+
}
368+
return (
369+
`v-model usage on component has changed in Vue 3. Component that expects ` +
370+
`to work with v-model should now use the "modelValue" prop and emit the ` +
371+
`"update:modelValue" event. You can update the usage and then ${configMsg}`
372+
)
373+
},
374+
link: `https://v3.vuejs.org/guide/migration/v-model.html`
375+
},
376+
348377
[DeprecationTypes.RENDER_FUNCTION]: {
349378
message:
350379
`Vue 3's render function API has changed. ` +
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { ShapeFlags } from '@vue/shared'
2+
import { ComponentInternalInstance, ComponentOptions } from '../component'
3+
import { callWithErrorHandling, ErrorCodes } from '../errorHandling'
4+
import { VNode } from '../vnode'
5+
import { popWarningContext, pushWarningContext } from '../warning'
6+
import { isCompatEnabled } from './compatConfig'
7+
import { DeprecationTypes, warnDeprecation } from './deprecations'
8+
9+
const defaultModelMapping = {
10+
prop: 'value',
11+
event: 'input'
12+
}
13+
14+
export const compatModelEventPrefix = `onModelCompat:`
15+
16+
const warnedTypes = new WeakSet()
17+
18+
export function convertLegacyVModelProps(vnode: VNode) {
19+
const { type, shapeFlag, props, dynamicProps } = vnode
20+
if (shapeFlag & ShapeFlags.COMPONENT && props && 'modelValue' in props) {
21+
if (
22+
!isCompatEnabled(
23+
DeprecationTypes.COMPONENT_V_MODEL,
24+
// this is a special case where we want to use the vnode component's
25+
// compat config instead of the current rendering instance (which is the
26+
// parent of the component that exposes v-model)
27+
{ type } as any
28+
)
29+
) {
30+
return
31+
}
32+
33+
if (__DEV__ && !warnedTypes.has(type as ComponentOptions)) {
34+
pushWarningContext(vnode)
35+
warnDeprecation(DeprecationTypes.COMPONENT_V_MODEL, { type } as any, type)
36+
popWarningContext()
37+
warnedTypes.add(type as ComponentOptions)
38+
}
39+
40+
const { prop, event } = (type as any).model || defaultModelMapping
41+
props[prop] = props.modelValue
42+
delete props.modelValue
43+
// important: update dynamic props
44+
if (dynamicProps) {
45+
dynamicProps[dynamicProps.indexOf('modelValue')] = prop
46+
}
47+
48+
props[compatModelEventPrefix + event] = props['onUpdate:modelValue']
49+
delete props['onUpdate:modelValue']
50+
}
51+
}
52+
53+
export function compatModelEmit(
54+
instance: ComponentInternalInstance,
55+
event: string,
56+
args: any[]
57+
) {
58+
if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) {
59+
return
60+
}
61+
const props = instance.vnode.props
62+
const modelHandler = props && props[compatModelEventPrefix + event]
63+
if (modelHandler) {
64+
callWithErrorHandling(
65+
modelHandler,
66+
instance,
67+
ErrorCodes.COMPONENT_EVENT_HANDLER,
68+
args
69+
)
70+
}
71+
}

packages/runtime-core/src/component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -687,9 +687,9 @@ export function finishComponentSetup(
687687
if (
688688
__COMPAT__ &&
689689
Component.render &&
690-
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION)
690+
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
691691
) {
692-
warnDeprecation(DeprecationTypes.RENDER_FUNCTION)
692+
warnDeprecation(DeprecationTypes.RENDER_FUNCTION, instance)
693693
const originalRender = Component.render
694694
Component.render = function compatRender() {
695695
return originalRender.call(this, compatH)

packages/runtime-core/src/componentEmits.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import { warn } from './warning'
2121
import { UnionToIntersection } from './helpers/typeUtils'
2222
import { devtoolsComponentEmit } from './devtools'
2323
import { AppContext } from './apiCreateApp'
24-
import { emit as compatEmit } from './compat/instanceEventEmitter'
24+
import { emit as compatInstanceEmit } from './compat/instanceEventEmitter'
25+
import { compatModelEventPrefix, compatModelEmit } from './compat/vModel'
2526

2627
export type ObjectEmitsOptions = Record<
2728
string,
@@ -57,7 +58,14 @@ export function emit(
5758
propsOptions: [propsOptions]
5859
} = instance
5960
if (emitsOptions) {
60-
if (!(event in emitsOptions) && !event.startsWith('hook:')) {
61+
if (
62+
!(event in emitsOptions) &&
63+
!(
64+
__COMPAT__ &&
65+
(event.startsWith('hook:') ||
66+
event.startsWith(compatModelEventPrefix))
67+
)
68+
) {
6169
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
6270
warn(
6371
`Component emitted event "${event}" but it is neither declared in ` +
@@ -151,7 +159,8 @@ export function emit(
151159
}
152160

153161
if (__COMPAT__) {
154-
return compatEmit(instance, event, args)
162+
compatModelEmit(instance, event, args)
163+
return compatInstanceEmit(instance, event, args)
155164
}
156165
}
157166

packages/runtime-core/src/componentProps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ function resolvePropValue(
348348
value = propsDefaults[key] = defaultValue.call(
349349
__COMPAT__ &&
350350
__DEV__ &&
351-
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS)
351+
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
352352
? createPropsDefaultThis(key)
353353
: null,
354354
props

packages/runtime-core/src/vnode.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
4242
import { hmrDirtyComponents } from './hmr'
4343
import { setCompiledSlotRendering } from './helpers/renderSlot'
4444
import { convertLegacyComponent } from './compat/component'
45+
import { convertLegacyVModelProps } from './compat/vModel'
4546

4647
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
4748
__isFragment: true
@@ -469,6 +470,10 @@ function _createVNode(
469470
currentBlock.push(vnode)
470471
}
471472

473+
if (__COMPAT__) {
474+
convertLegacyVModelProps(vnode)
475+
}
476+
472477
return vnode
473478
}
474479

0 commit comments

Comments
 (0)