diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 99eaf02bb..680f0c696 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -118,7 +118,6 @@ export interface RendererOptions< parentSuspense?: SuspenseBoundary | null, unmountChildren?: UnmountChildrenFn ): void - forcePatchProp?(el: HostElement, key: string): boolean insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void remove(el: HostNode): void createElement( @@ -288,99 +287,6 @@ export const queuePostRenderEffect = __FEATURE_SUSPENSE__ ? queueEffectWithSuspense : queuePostFlushCb -export const setRef = ( - rawRef: VNodeNormalizedRef, - oldRawRef: VNodeNormalizedRef | null, - parentSuspense: SuspenseBoundary | null, - vnode: VNode, - isUnmount = false -) => { - if (isArray(rawRef)) { - rawRef.forEach((r, i) => - setRef( - r, - oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), - parentSuspense, - vnode, - isUnmount - ) - ) - return - } - - if (isAsyncWrapper(vnode) && !isUnmount) { - // when mounting async components, nothing needs to be done, - // because the template ref is forwarded to inner component - return - } - - const refValue = - vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT - ? getExposeProxy(vnode.component!) || vnode.component!.proxy - : vnode.el - const value = isUnmount ? null : refValue - - const { i: owner, r: ref } = rawRef - if (__DEV__ && !owner) { - warn( - `Missing ref owner context. ref cannot be used on hoisted vnodes. ` + - `A vnode with ref must be created inside the render function.` - ) - return - } - const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r - const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs - const setupState = owner.setupState - - // dynamic ref changed. unset old ref - if (oldRef != null && oldRef !== ref) { - if (isString(oldRef)) { - refs[oldRef] = null - if (hasOwn(setupState, oldRef)) { - setupState[oldRef] = null - } - } else if (isRef(oldRef)) { - oldRef.value = null - } - } - - if (isString(ref)) { - const doSet = () => { - if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) { - registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount) - } else { - refs[ref] = value - } - if (hasOwn(setupState, ref)) { - setupState[ref] = value - } - } - // #1789: for non-null values, set them after render - // null values means this is unmount and it should not overwrite another - // ref with the same key - if (value) { - ;(doSet as SchedulerJob).id = -1 - queuePostRenderEffect(doSet, parentSuspense) - } else { - doSet() - } - } else if (isRef(ref)) { - const doSet = () => { - ref.value = value - } - if (value) { - ;(doSet as SchedulerJob).id = -1 - queuePostRenderEffect(doSet, parentSuspense) - } else { - doSet() - } - } else if (isFunction(ref)) { - callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs]) - } else if (__DEV__) { - warn('Invalid template ref type:', value, `(${typeof value})`) - } -} - /** * The createRenderer function accepts two generic arguments: * HostNode and HostElement, corresponding to Node and Element types in the @@ -444,7 +350,6 @@ function baseCreateRenderer( insert: hostInsert, remove: hostRemove, patchProp: hostPatchProp, - forcePatchProp: hostForcePatchProp, createElement: hostCreateElement, createText: hostCreateText, createComment: hostCreateComment, @@ -767,7 +672,7 @@ function baseCreateRenderer( // props if (props) { for (const key in props) { - if (!isReservedProp(key)) { + if (key !== 'value' && !isReservedProp(key)) { hostPatchProp( el, key, @@ -781,6 +686,18 @@ function baseCreateRenderer( ) } } + /** + * Special case for setting value on DOM elements: + * - it can be order-sensitive (e.g. should be set *after* min/max, #2325, #4024) + * - it needs to be forced (#1471) + * #2353 proposes adding another renderer option to configure this, but + * the properties affects are so finite it is worth special casing it + * here to reduce the complexity. (Special casing it also should not + * affect non-DOM renderers) + */ + if ('value' in props) { + hostPatchProp(el, 'value', null, props.value) + } if ((vnodeHook = props.onVnodeBeforeMount)) { invokeVNodeHook(vnodeHook, parentComponent, vnode) } @@ -967,10 +884,8 @@ function baseCreateRenderer( const key = propsToUpdate[i] const prev = oldProps[key] const next = newProps[key] - if ( - next !== prev || - (hostForcePatchProp && hostForcePatchProp(el, key)) - ) { + // #1471 force patch value + if (next !== prev || key === 'value') { hostPatchProp( el, key, @@ -1104,10 +1019,8 @@ function baseCreateRenderer( if (isReservedProp(key)) continue const next = newProps[key] const prev = oldProps[key] - if ( - next !== prev || - (hostForcePatchProp && hostForcePatchProp(el, key)) - ) { + // defer patching value + if (next !== prev && key !== 'value') { hostPatchProp( el, key, @@ -1138,6 +1051,9 @@ function baseCreateRenderer( } } } + if ('value' in newProps) { + hostPatchProp(el, 'value', oldProps.value, newProps.value) + } } } @@ -2418,6 +2334,99 @@ function baseCreateRenderer( } } +export function setRef( + rawRef: VNodeNormalizedRef, + oldRawRef: VNodeNormalizedRef | null, + parentSuspense: SuspenseBoundary | null, + vnode: VNode, + isUnmount = false +) { + if (isArray(rawRef)) { + rawRef.forEach((r, i) => + setRef( + r, + oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef), + parentSuspense, + vnode, + isUnmount + ) + ) + return + } + + if (isAsyncWrapper(vnode) && !isUnmount) { + // when mounting async components, nothing needs to be done, + // because the template ref is forwarded to inner component + return + } + + const refValue = + vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT + ? getExposeProxy(vnode.component!) || vnode.component!.proxy + : vnode.el + const value = isUnmount ? null : refValue + + const { i: owner, r: ref } = rawRef + if (__DEV__ && !owner) { + warn( + `Missing ref owner context. ref cannot be used on hoisted vnodes. ` + + `A vnode with ref must be created inside the render function.` + ) + return + } + const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r + const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs + const setupState = owner.setupState + + // dynamic ref changed. unset old ref + if (oldRef != null && oldRef !== ref) { + if (isString(oldRef)) { + refs[oldRef] = null + if (hasOwn(setupState, oldRef)) { + setupState[oldRef] = null + } + } else if (isRef(oldRef)) { + oldRef.value = null + } + } + + if (isString(ref)) { + const doSet = () => { + if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) { + registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount) + } else { + refs[ref] = value + } + if (hasOwn(setupState, ref)) { + setupState[ref] = value + } + } + // #1789: for non-null values, set them after render + // null values means this is unmount and it should not overwrite another + // ref with the same key + if (value) { + ;(doSet as SchedulerJob).id = -1 + queuePostRenderEffect(doSet, parentSuspense) + } else { + doSet() + } + } else if (isRef(ref)) { + const doSet = () => { + ref.value = value + } + if (value) { + ;(doSet as SchedulerJob).id = -1 + queuePostRenderEffect(doSet, parentSuspense) + } else { + doSet() + } + } else if (isFunction(ref)) { + callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs]) + } else if (__DEV__) { + warn('Invalid template ref type:', value, `(${typeof value})`) + } +} + export function invokeVNodeHook( hook: VNodeHook, instance: ComponentInternalInstance | null, diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index 257c554a6..8a3012c20 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -13,7 +13,7 @@ import { compatUtils } from '@vue/runtime-core' import { nodeOps } from './nodeOps' -import { patchProp, forcePatchProp } from './patchProp' +import { patchProp } from './patchProp' // Importing from the compiler, will be tree-shaken in prod import { isFunction, isString, isHTMLTag, isSVGTag, extend } from '@vue/shared' @@ -24,7 +24,7 @@ declare module '@vue/reactivity' { } } -const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps) +const rendererOptions = extend({ patchProp }, nodeOps) // lazy create the renderer - this makes core renderer logic tree-shakable // in case the user only imports reactivity utilities from Vue. diff --git a/packages/runtime-dom/src/patchProp.ts b/packages/runtime-dom/src/patchProp.ts index 47d4f3ba4..c92bbfb56 100644 --- a/packages/runtime-dom/src/patchProp.ts +++ b/packages/runtime-dom/src/patchProp.ts @@ -10,9 +10,6 @@ const nativeOnRE = /^on[a-z]/ type DOMRendererOptions = RendererOptions -export const forcePatchProp: DOMRendererOptions['forcePatchProp'] = (_, key) => - key === 'value' - export const patchProp: DOMRendererOptions['patchProp'] = ( el, key,