Skip to content

Commit

Permalink
fix(runtime-dom): defer setting value
Browse files Browse the repository at this point in the history
fix #2325, fix #4024
  • Loading branch information
yyx990803 committed Jul 21, 2021
1 parent 36ae23d commit ff0c810
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 109 deletions.
217 changes: 113 additions & 104 deletions packages/runtime-core/src/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -444,7 +350,6 @@ function baseCreateRenderer(
insert: hostInsert,
remove: hostRemove,
patchProp: hostPatchProp,
forcePatchProp: hostForcePatchProp,
createElement: hostCreateElement,
createText: hostCreateText,
createComment: hostCreateComment,
Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1138,6 +1051,9 @@ function baseCreateRenderer(
}
}
}
if ('value' in newProps) {
hostPatchProp(el, 'value', oldProps.value, newProps.value)
}
}
}

Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/runtime-dom/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -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.
Expand Down
3 changes: 0 additions & 3 deletions packages/runtime-dom/src/patchProp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,6 @@ const nativeOnRE = /^on[a-z]/

type DOMRendererOptions = RendererOptions<Node, Element>

export const forcePatchProp: DOMRendererOptions['forcePatchProp'] = (_, key) =>
key === 'value'

export const patchProp: DOMRendererOptions['patchProp'] = (
el,
key,
Expand Down

0 comments on commit ff0c810

Please sign in to comment.