Skip to content

Commit ff0c810

Browse files
committed
fix(runtime-dom): defer setting value
fix #2325, fix #4024
1 parent 36ae23d commit ff0c810

File tree

3 files changed

+115
-109
lines changed

3 files changed

+115
-109
lines changed

Diff for: packages/runtime-core/src/renderer.ts

+113-104
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,6 @@ export interface RendererOptions<
118118
parentSuspense?: SuspenseBoundary | null,
119119
unmountChildren?: UnmountChildrenFn
120120
): void
121-
forcePatchProp?(el: HostElement, key: string): boolean
122121
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
123122
remove(el: HostNode): void
124123
createElement(
@@ -288,99 +287,6 @@ export const queuePostRenderEffect = __FEATURE_SUSPENSE__
288287
? queueEffectWithSuspense
289288
: queuePostFlushCb
290289

291-
export const setRef = (
292-
rawRef: VNodeNormalizedRef,
293-
oldRawRef: VNodeNormalizedRef | null,
294-
parentSuspense: SuspenseBoundary | null,
295-
vnode: VNode,
296-
isUnmount = false
297-
) => {
298-
if (isArray(rawRef)) {
299-
rawRef.forEach((r, i) =>
300-
setRef(
301-
r,
302-
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
303-
parentSuspense,
304-
vnode,
305-
isUnmount
306-
)
307-
)
308-
return
309-
}
310-
311-
if (isAsyncWrapper(vnode) && !isUnmount) {
312-
// when mounting async components, nothing needs to be done,
313-
// because the template ref is forwarded to inner component
314-
return
315-
}
316-
317-
const refValue =
318-
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
319-
? getExposeProxy(vnode.component!) || vnode.component!.proxy
320-
: vnode.el
321-
const value = isUnmount ? null : refValue
322-
323-
const { i: owner, r: ref } = rawRef
324-
if (__DEV__ && !owner) {
325-
warn(
326-
`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
327-
`A vnode with ref must be created inside the render function.`
328-
)
329-
return
330-
}
331-
const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
332-
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
333-
const setupState = owner.setupState
334-
335-
// dynamic ref changed. unset old ref
336-
if (oldRef != null && oldRef !== ref) {
337-
if (isString(oldRef)) {
338-
refs[oldRef] = null
339-
if (hasOwn(setupState, oldRef)) {
340-
setupState[oldRef] = null
341-
}
342-
} else if (isRef(oldRef)) {
343-
oldRef.value = null
344-
}
345-
}
346-
347-
if (isString(ref)) {
348-
const doSet = () => {
349-
if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) {
350-
registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount)
351-
} else {
352-
refs[ref] = value
353-
}
354-
if (hasOwn(setupState, ref)) {
355-
setupState[ref] = value
356-
}
357-
}
358-
// #1789: for non-null values, set them after render
359-
// null values means this is unmount and it should not overwrite another
360-
// ref with the same key
361-
if (value) {
362-
;(doSet as SchedulerJob).id = -1
363-
queuePostRenderEffect(doSet, parentSuspense)
364-
} else {
365-
doSet()
366-
}
367-
} else if (isRef(ref)) {
368-
const doSet = () => {
369-
ref.value = value
370-
}
371-
if (value) {
372-
;(doSet as SchedulerJob).id = -1
373-
queuePostRenderEffect(doSet, parentSuspense)
374-
} else {
375-
doSet()
376-
}
377-
} else if (isFunction(ref)) {
378-
callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
379-
} else if (__DEV__) {
380-
warn('Invalid template ref type:', value, `(${typeof value})`)
381-
}
382-
}
383-
384290
/**
385291
* The createRenderer function accepts two generic arguments:
386292
* HostNode and HostElement, corresponding to Node and Element types in the
@@ -444,7 +350,6 @@ function baseCreateRenderer(
444350
insert: hostInsert,
445351
remove: hostRemove,
446352
patchProp: hostPatchProp,
447-
forcePatchProp: hostForcePatchProp,
448353
createElement: hostCreateElement,
449354
createText: hostCreateText,
450355
createComment: hostCreateComment,
@@ -767,7 +672,7 @@ function baseCreateRenderer(
767672
// props
768673
if (props) {
769674
for (const key in props) {
770-
if (!isReservedProp(key)) {
675+
if (key !== 'value' && !isReservedProp(key)) {
771676
hostPatchProp(
772677
el,
773678
key,
@@ -781,6 +686,18 @@ function baseCreateRenderer(
781686
)
782687
}
783688
}
689+
/**
690+
* Special case for setting value on DOM elements:
691+
* - it can be order-sensitive (e.g. should be set *after* min/max, #2325, #4024)
692+
* - it needs to be forced (#1471)
693+
* #2353 proposes adding another renderer option to configure this, but
694+
* the properties affects are so finite it is worth special casing it
695+
* here to reduce the complexity. (Special casing it also should not
696+
* affect non-DOM renderers)
697+
*/
698+
if ('value' in props) {
699+
hostPatchProp(el, 'value', null, props.value)
700+
}
784701
if ((vnodeHook = props.onVnodeBeforeMount)) {
785702
invokeVNodeHook(vnodeHook, parentComponent, vnode)
786703
}
@@ -967,10 +884,8 @@ function baseCreateRenderer(
967884
const key = propsToUpdate[i]
968885
const prev = oldProps[key]
969886
const next = newProps[key]
970-
if (
971-
next !== prev ||
972-
(hostForcePatchProp && hostForcePatchProp(el, key))
973-
) {
887+
// #1471 force patch value
888+
if (next !== prev || key === 'value') {
974889
hostPatchProp(
975890
el,
976891
key,
@@ -1104,10 +1019,8 @@ function baseCreateRenderer(
11041019
if (isReservedProp(key)) continue
11051020
const next = newProps[key]
11061021
const prev = oldProps[key]
1107-
if (
1108-
next !== prev ||
1109-
(hostForcePatchProp && hostForcePatchProp(el, key))
1110-
) {
1022+
// defer patching value
1023+
if (next !== prev && key !== 'value') {
11111024
hostPatchProp(
11121025
el,
11131026
key,
@@ -1138,6 +1051,9 @@ function baseCreateRenderer(
11381051
}
11391052
}
11401053
}
1054+
if ('value' in newProps) {
1055+
hostPatchProp(el, 'value', oldProps.value, newProps.value)
1056+
}
11411057
}
11421058
}
11431059

@@ -2418,6 +2334,99 @@ function baseCreateRenderer(
24182334
}
24192335
}
24202336

2337+
export function setRef(
2338+
rawRef: VNodeNormalizedRef,
2339+
oldRawRef: VNodeNormalizedRef | null,
2340+
parentSuspense: SuspenseBoundary | null,
2341+
vnode: VNode,
2342+
isUnmount = false
2343+
) {
2344+
if (isArray(rawRef)) {
2345+
rawRef.forEach((r, i) =>
2346+
setRef(
2347+
r,
2348+
oldRawRef && (isArray(oldRawRef) ? oldRawRef[i] : oldRawRef),
2349+
parentSuspense,
2350+
vnode,
2351+
isUnmount
2352+
)
2353+
)
2354+
return
2355+
}
2356+
2357+
if (isAsyncWrapper(vnode) && !isUnmount) {
2358+
// when mounting async components, nothing needs to be done,
2359+
// because the template ref is forwarded to inner component
2360+
return
2361+
}
2362+
2363+
const refValue =
2364+
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
2365+
? getExposeProxy(vnode.component!) || vnode.component!.proxy
2366+
: vnode.el
2367+
const value = isUnmount ? null : refValue
2368+
2369+
const { i: owner, r: ref } = rawRef
2370+
if (__DEV__ && !owner) {
2371+
warn(
2372+
`Missing ref owner context. ref cannot be used on hoisted vnodes. ` +
2373+
`A vnode with ref must be created inside the render function.`
2374+
)
2375+
return
2376+
}
2377+
const oldRef = oldRawRef && (oldRawRef as VNodeNormalizedRefAtom).r
2378+
const refs = owner.refs === EMPTY_OBJ ? (owner.refs = {}) : owner.refs
2379+
const setupState = owner.setupState
2380+
2381+
// dynamic ref changed. unset old ref
2382+
if (oldRef != null && oldRef !== ref) {
2383+
if (isString(oldRef)) {
2384+
refs[oldRef] = null
2385+
if (hasOwn(setupState, oldRef)) {
2386+
setupState[oldRef] = null
2387+
}
2388+
} else if (isRef(oldRef)) {
2389+
oldRef.value = null
2390+
}
2391+
}
2392+
2393+
if (isString(ref)) {
2394+
const doSet = () => {
2395+
if (__COMPAT__ && isCompatEnabled(DeprecationTypes.V_FOR_REF, owner)) {
2396+
registerLegacyRef(refs, ref, refValue, owner, rawRef.f, isUnmount)
2397+
} else {
2398+
refs[ref] = value
2399+
}
2400+
if (hasOwn(setupState, ref)) {
2401+
setupState[ref] = value
2402+
}
2403+
}
2404+
// #1789: for non-null values, set them after render
2405+
// null values means this is unmount and it should not overwrite another
2406+
// ref with the same key
2407+
if (value) {
2408+
;(doSet as SchedulerJob).id = -1
2409+
queuePostRenderEffect(doSet, parentSuspense)
2410+
} else {
2411+
doSet()
2412+
}
2413+
} else if (isRef(ref)) {
2414+
const doSet = () => {
2415+
ref.value = value
2416+
}
2417+
if (value) {
2418+
;(doSet as SchedulerJob).id = -1
2419+
queuePostRenderEffect(doSet, parentSuspense)
2420+
} else {
2421+
doSet()
2422+
}
2423+
} else if (isFunction(ref)) {
2424+
callWithErrorHandling(ref, owner, ErrorCodes.FUNCTION_REF, [value, refs])
2425+
} else if (__DEV__) {
2426+
warn('Invalid template ref type:', value, `(${typeof value})`)
2427+
}
2428+
}
2429+
24212430
export function invokeVNodeHook(
24222431
hook: VNodeHook,
24232432
instance: ComponentInternalInstance | null,

Diff for: packages/runtime-dom/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
compatUtils
1414
} from '@vue/runtime-core'
1515
import { nodeOps } from './nodeOps'
16-
import { patchProp, forcePatchProp } from './patchProp'
16+
import { patchProp } from './patchProp'
1717
// Importing from the compiler, will be tree-shaken in prod
1818
import { isFunction, isString, isHTMLTag, isSVGTag, extend } from '@vue/shared'
1919

@@ -24,7 +24,7 @@ declare module '@vue/reactivity' {
2424
}
2525
}
2626

27-
const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps)
27+
const rendererOptions = extend({ patchProp }, nodeOps)
2828

2929
// lazy create the renderer - this makes core renderer logic tree-shakable
3030
// in case the user only imports reactivity utilities from Vue.

Diff for: packages/runtime-dom/src/patchProp.ts

-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ const nativeOnRE = /^on[a-z]/
1010

1111
type DOMRendererOptions = RendererOptions<Node, Element>
1212

13-
export const forcePatchProp: DOMRendererOptions['forcePatchProp'] = (_, key) =>
14-
key === 'value'
15-
1613
export const patchProp: DOMRendererOptions['patchProp'] = (
1714
el,
1815
key,

0 commit comments

Comments
 (0)