From: Evan You Date: Sat, 10 Apr 2021 03:10:29 +0000 (-0400) Subject: wip: component v-model compat X-Git-Tag: v3.1.0-beta.1~59^2~47 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=183f9b0013312e4f86d95d08e69d4af5abbdf529;p=thirdparty%2Fvuejs%2Fcore.git wip: component v-model compat --- diff --git a/packages/runtime-core/src/compat/deprecations.ts b/packages/runtime-core/src/compat/deprecations.ts index b2d10f52d4..8a00ab0e4b 100644 --- a/packages/runtime-core/src/compat/deprecations.ts +++ b/packages/runtime-core/src/compat/deprecations.ts @@ -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 = { 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. ` + diff --git a/packages/runtime-core/src/compat/vModel.ts b/packages/runtime-core/src/compat/vModel.ts new file mode 100644 index 0000000000..d0d8c6af89 --- /dev/null +++ b/packages/runtime-core/src/compat/vModel.ts @@ -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 + ) + } +} diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 1045db8c89..a2be7de7ca 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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) diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index a4ffdf89b3..ca04092a0e 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -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) } } diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 7725b18bb0..46a6b5ef49 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -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 diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 35aa6678b2..9d1fd791ab 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -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 }