wip: component v-model compat
This commit is contained in:
parent
f05d6dfd98
commit
183f9b0013
@ -1,5 +1,7 @@
|
|||||||
|
import { hasOwn, isArray } from '@vue/shared/src'
|
||||||
import {
|
import {
|
||||||
ComponentInternalInstance,
|
ComponentInternalInstance,
|
||||||
|
ComponentOptions,
|
||||||
formatComponentName,
|
formatComponentName,
|
||||||
getComponentName,
|
getComponentName,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
@ -52,6 +54,7 @@ export const enum DeprecationTypes {
|
|||||||
|
|
||||||
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
|
COMPONENT_ASYNC = 'COMPONENT_ASYNC',
|
||||||
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
|
COMPONENT_FUNCTIONAL = 'COMPONENT_FUNCTIONAL',
|
||||||
|
COMPONENT_V_MODEL = 'COMPONENT_V_MODEL',
|
||||||
|
|
||||||
RENDER_FUNCTION = 'RENDER_FUNCTION'
|
RENDER_FUNCTION = 'RENDER_FUNCTION'
|
||||||
}
|
}
|
||||||
@ -345,6 +348,32 @@ const deprecationData: Record<DeprecationTypes, DeprecationData> = {
|
|||||||
link: `https://v3.vuejs.org/guide/migration/functional-components.html`
|
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]: {
|
[DeprecationTypes.RENDER_FUNCTION]: {
|
||||||
message:
|
message:
|
||||||
`Vue 3's render function API has changed. ` +
|
`Vue 3's render function API has changed. ` +
|
||||||
|
71
packages/runtime-core/src/compat/vModel.ts
Normal file
71
packages/runtime-core/src/compat/vModel.ts
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -687,9 +687,9 @@ export function finishComponentSetup(
|
|||||||
if (
|
if (
|
||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
Component.render &&
|
Component.render &&
|
||||||
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION)
|
isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)
|
||||||
) {
|
) {
|
||||||
warnDeprecation(DeprecationTypes.RENDER_FUNCTION)
|
warnDeprecation(DeprecationTypes.RENDER_FUNCTION, instance)
|
||||||
const originalRender = Component.render
|
const originalRender = Component.render
|
||||||
Component.render = function compatRender() {
|
Component.render = function compatRender() {
|
||||||
return originalRender.call(this, compatH)
|
return originalRender.call(this, compatH)
|
||||||
|
@ -21,7 +21,8 @@ import { warn } from './warning'
|
|||||||
import { UnionToIntersection } from './helpers/typeUtils'
|
import { UnionToIntersection } from './helpers/typeUtils'
|
||||||
import { devtoolsComponentEmit } from './devtools'
|
import { devtoolsComponentEmit } from './devtools'
|
||||||
import { AppContext } from './apiCreateApp'
|
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<
|
export type ObjectEmitsOptions = Record<
|
||||||
string,
|
string,
|
||||||
@ -57,7 +58,14 @@ export function emit(
|
|||||||
propsOptions: [propsOptions]
|
propsOptions: [propsOptions]
|
||||||
} = instance
|
} = instance
|
||||||
if (emitsOptions) {
|
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)) {
|
if (!propsOptions || !(toHandlerKey(event) in propsOptions)) {
|
||||||
warn(
|
warn(
|
||||||
`Component emitted event "${event}" but it is neither declared in ` +
|
`Component emitted event "${event}" but it is neither declared in ` +
|
||||||
@ -151,7 +159,8 @@ export function emit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
return compatEmit(instance, event, args)
|
compatModelEmit(instance, event, args)
|
||||||
|
return compatInstanceEmit(instance, event, args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +348,7 @@ function resolvePropValue(
|
|||||||
value = propsDefaults[key] = defaultValue.call(
|
value = propsDefaults[key] = defaultValue.call(
|
||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS)
|
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
|
||||||
? createPropsDefaultThis(key)
|
? createPropsDefaultThis(key)
|
||||||
: null,
|
: null,
|
||||||
props
|
props
|
||||||
|
@ -42,6 +42,7 @@ import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
|
|||||||
import { hmrDirtyComponents } from './hmr'
|
import { hmrDirtyComponents } from './hmr'
|
||||||
import { setCompiledSlotRendering } from './helpers/renderSlot'
|
import { setCompiledSlotRendering } from './helpers/renderSlot'
|
||||||
import { convertLegacyComponent } from './compat/component'
|
import { convertLegacyComponent } from './compat/component'
|
||||||
|
import { convertLegacyVModelProps } from './compat/vModel'
|
||||||
|
|
||||||
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
|
||||||
__isFragment: true
|
__isFragment: true
|
||||||
@ -469,6 +470,10 @@ function _createVNode(
|
|||||||
currentBlock.push(vnode)
|
currentBlock.push(vnode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__COMPAT__) {
|
||||||
|
convertLegacyVModelProps(vnode)
|
||||||
|
}
|
||||||
|
|
||||||
return vnode
|
return vnode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user