wip: component v-model compat

This commit is contained in:
Evan You 2021-04-09 23:10:29 -04:00
parent f05d6dfd98
commit 183f9b0013
6 changed files with 120 additions and 6 deletions

View File

@ -1,5 +1,7 @@
import { hasOwn, isArray } from '@vue/shared/src'
import {
ComponentInternalInstance,
ComponentOptions,
formatComponentName,
getComponentName,
getCurrentInstance,
@ -52,6 +54,7 @@ export const enum DeprecationTypes {
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
RENDER_FUNCTION = 'RENDER_FUNCTION'
}
@ -345,6 +348,32 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
link: `https://v3.vuejs.org/guide/migration/functional-components.html`
},
[DeprecationTypes.COMPONENT_V_MODEL]: {
message: (comp: ComponentOptions) => {
const configMsg =
`opt-in to ` +
`Vue 3 behavior on a per-component basis with \`compatConfig: { ${
DeprecationTypes.COMPONENT_V_MODEL
}: false }\`.`
if (
comp.props && isArray(comp.props)
? comp.props.includes('modelValue')
: hasOwn(comp.props, 'modelValue')
) {
return (
`Component delcares "modelValue" prop, which is Vue 3 usage, but ` +
`is running under Vue 2 compat v-model behavior. You can ${configMsg}`
)
}
return (
`v-model usage on component has changed in Vue 3. Component that expects ` +
`to work with v-model should now use the "modelValue" prop and emit the ` +
`"update:modelValue" event. You can update the usage and then ${configMsg}`
)
},
link: `https://v3.vuejs.org/guide/migration/v-model.html`
},
[DeprecationTypes.RENDER_FUNCTION]: {
message:
`Vue 3's render function API has changed. ` +

View File

@ -0,0 +1,71 @@
import { ShapeFlags } from '@vue/shared'
import { ComponentInternalInstance, ComponentOptions } from '../component'
import { callWithErrorHandling, ErrorCodes } from '../errorHandling'
import { VNode } from '../vnode'
import { popWarningContext, pushWarningContext } from '../warning'
import { isCompatEnabled } from './compatConfig'
import { DeprecationTypes, warnDeprecation } from './deprecations'
const defaultModelMapping = {
prop: 'value',
event: 'input'
}
export const compatModelEventPrefix = `onModelCompat:`
const warnedTypes = new WeakSet()
export function convertLegacyVModelProps(vnode: VNode) {
const { type, shapeFlag, props, dynamicProps } = vnode
if (shapeFlag & ShapeFlags.COMPONENT && props && 'modelValue' in props) {
if (
!isCompatEnabled(
DeprecationTypes.COMPONENT_V_MODEL,
// this is a special case where we want to use the vnode component's
// compat config instead of the current rendering instance (which is the
// parent of the component that exposes v-model)
{ type } as any
)
) {
return
}
if (__DEV__ && !warnedTypes.has(type as ComponentOptions)) {
pushWarningContext(vnode)
warnDeprecation(DeprecationTypes.COMPONENT_V_MODEL, { type } as any, type)
popWarningContext()
warnedTypes.add(type as ComponentOptions)
}
const { prop, event } = (type as any).model || defaultModelMapping
props[prop] = props.modelValue
delete props.modelValue
// important: update dynamic props
if (dynamicProps) {
dynamicProps[dynamicProps.indexOf('modelValue')] = prop
}
props[compatModelEventPrefix + event] = props['onUpdate:modelValue']
delete props['onUpdate:modelValue']
}
}
export function compatModelEmit(
instance: ComponentInternalInstance,
event: string,
args: any[]
) {
if (!isCompatEnabled(DeprecationTypes.COMPONENT_V_MODEL, instance)) {
return
}
const props = instance.vnode.props
const modelHandler = props && props[compatModelEventPrefix + event]
if (modelHandler) {
callWithErrorHandling(
modelHandler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
args
)
}
}

View File

@ -687,9 +687,9 @@ export function finishComponentSetup(
if (
__COMPAT__ &&
Component.render &&
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION)
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
) {
warnDeprecation(DeprecationTypes.RENDER_FUNCTION)
warnDeprecation(DeprecationTypes.RENDER_FUNCTION, instance)
const originalRender = Component.render
Component.render = function compatRender() {
return originalRender.call(this, compatH)

View File

@ -21,7 +21,8 @@ import { warn } from './warning'
import { UnionToIntersection } from './helpers/typeUtils'
import { devtoolsComponentEmit } from './devtools'
import { AppContext } from './apiCreateApp'
import { emit as compatEmit } from './compat/instanceEventEmitter'
import { emit as compatInstanceEmit } from './compat/instanceEventEmitter'
import { compatModelEventPrefix, compatModelEmit } from './compat/vModel'
export type ObjectEmitsOptions = Record<
string,
@ -57,7 +58,14 @@ export function emit(
propsOptions: [propsOptions]
} = instance
if (emitsOptions) {
if (!(event in emitsOptions) && !event.startsWith('hook:')) {
if (
!(event in emitsOptions) &&
!(
__COMPAT__ &&
(event.startsWith('hook:') ||
event.startsWith(compatModelEventPrefix))
)
) {
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
warn(
`Component emitted event "${event}" but it is neither declared in ` +
@ -151,7 +159,8 @@ export function emit(
}
if (__COMPAT__) {
return compatEmit(instance, event, args)
compatModelEmit(instance, event, args)
return compatInstanceEmit(instance, event, args)
}
}

View File

@ -348,7 +348,7 @@ function resolvePropValue(
value = propsDefaults[key] = defaultValue.call(
__COMPAT__ &&
__DEV__ &&
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS)
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
? createPropsDefaultThis(key)
: null,
props

View File

@ -42,6 +42,7 @@ import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
import { hmrDirtyComponents } from './hmr'
import { setCompiledSlotRendering } from './helpers/renderSlot'
import { convertLegacyComponent } from './compat/component'
import { convertLegacyVModelProps } from './compat/vModel'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true
@ -469,6 +470,10 @@ function _createVNode(
currentBlock.push(vnode)
}
if (__COMPAT__) {
convertLegacyVModelProps(vnode)
}
return vnode
}