mirror of https://github.com/vuejs/core.git
fix(runtime-dom): properly handle innerHTML unmount into new children (#11159)
close #9135
This commit is contained in:
parent
b287aeec3e
commit
3e9e32ee0a
|
@ -465,15 +465,7 @@ export function createHydrationFunctions(
|
|||
// force hydrate v-bind with .prop modifiers
|
||||
key[0] === '.'
|
||||
) {
|
||||
patchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent,
|
||||
)
|
||||
patchProp(el, key, null, props[key], undefined, parentComponent)
|
||||
}
|
||||
}
|
||||
} else if (props.onClick) {
|
||||
|
@ -485,7 +477,6 @@ export function createHydrationFunctions(
|
|||
null,
|
||||
props.onClick,
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent,
|
||||
)
|
||||
} else if (patchFlag & PatchFlags.STYLE && isReactive(props.style)) {
|
||||
|
|
|
@ -107,10 +107,7 @@ export interface RendererOptions<
|
|||
prevValue: any,
|
||||
nextValue: any,
|
||||
namespace?: ElementNamespace,
|
||||
prevChildren?: VNode<HostNode, HostElement>[],
|
||||
parentComponent?: ComponentInternalInstance | null,
|
||||
parentSuspense?: SuspenseBoundary | null,
|
||||
unmountChildren?: UnmountChildrenFn,
|
||||
): void
|
||||
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
|
||||
remove(el: HostNode): void
|
||||
|
@ -670,17 +667,7 @@ function baseCreateRenderer(
|
|||
if (props) {
|
||||
for (const key in props) {
|
||||
if (key !== 'value' && !isReservedProp(key)) {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, null, props[key], namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -833,6 +820,15 @@ function baseCreateRenderer(
|
|||
dynamicChildren = null
|
||||
}
|
||||
|
||||
// #9135 innerHTML / textContent unset needs to happen before possible
|
||||
// new children mount
|
||||
if (
|
||||
(oldProps.innerHTML && newProps.innerHTML == null) ||
|
||||
(oldProps.textContent && newProps.textContent == null)
|
||||
) {
|
||||
hostSetElementText(el, '')
|
||||
}
|
||||
|
||||
if (dynamicChildren) {
|
||||
patchBlockChildren(
|
||||
n1.dynamicChildren!,
|
||||
|
@ -869,15 +865,7 @@ function baseCreateRenderer(
|
|||
// (i.e. at the exact same position in the source template)
|
||||
if (patchFlag & PatchFlags.FULL_PROPS) {
|
||||
// element props contain dynamic keys, full diff needed
|
||||
patchProps(
|
||||
el,
|
||||
n2,
|
||||
oldProps,
|
||||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
namespace,
|
||||
)
|
||||
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||
} else {
|
||||
// class
|
||||
// this flag is matched when the element has dynamic class bindings.
|
||||
|
@ -908,17 +896,7 @@ function baseCreateRenderer(
|
|||
const next = newProps[key]
|
||||
// #1471 force patch value
|
||||
if (next !== prev || key === 'value') {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
prev,
|
||||
next,
|
||||
namespace,
|
||||
n1.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -933,15 +911,7 @@ function baseCreateRenderer(
|
|||
}
|
||||
} else if (!optimized && dynamicChildren == null) {
|
||||
// unoptimized, full diff
|
||||
patchProps(
|
||||
el,
|
||||
n2,
|
||||
oldProps,
|
||||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
namespace,
|
||||
)
|
||||
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||
}
|
||||
|
||||
if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
|
||||
|
@ -998,11 +968,9 @@ function baseCreateRenderer(
|
|||
|
||||
const patchProps = (
|
||||
el: RendererElement,
|
||||
vnode: VNode,
|
||||
oldProps: Data,
|
||||
newProps: Data,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
namespace: ElementNamespace,
|
||||
) => {
|
||||
if (oldProps !== newProps) {
|
||||
|
@ -1015,10 +983,7 @@ function baseCreateRenderer(
|
|||
oldProps[key],
|
||||
null,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1030,17 +995,7 @@ function baseCreateRenderer(
|
|||
const prev = oldProps[key]
|
||||
// defer patching value
|
||||
if (next !== prev && key !== 'value') {
|
||||
hostPatchProp(
|
||||
el,
|
||||
key,
|
||||
prev,
|
||||
next,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||
}
|
||||
}
|
||||
if ('value' in newProps) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { patchProp } from '../src/patchProp'
|
||||
import { h, render } from '../src'
|
||||
import { h, nextTick, ref, render } from '../src'
|
||||
|
||||
describe('runtime-dom: props patching', () => {
|
||||
test('basic', () => {
|
||||
|
@ -133,6 +133,25 @@ describe('runtime-dom: props patching', () => {
|
|||
expect(fn).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('patch innerHTML porp', async () => {
|
||||
const root = document.createElement('div')
|
||||
const state = ref(false)
|
||||
const Comp = {
|
||||
render: () => {
|
||||
if (state.value) {
|
||||
return h('div', [h('del', null, 'baz')])
|
||||
} else {
|
||||
return h('div', { innerHTML: 'baz' })
|
||||
}
|
||||
},
|
||||
}
|
||||
render(h(Comp), root)
|
||||
expect(root.innerHTML).toBe(`<div>baz</div>`)
|
||||
state.value = true
|
||||
await nextTick()
|
||||
expect(root.innerHTML).toBe(`<div><del>baz</del></div>`)
|
||||
})
|
||||
|
||||
test('textContent unmount prev children', () => {
|
||||
const fn = vi.fn()
|
||||
const comp = {
|
||||
|
|
|
@ -10,19 +10,13 @@ export function patchDOMProp(
|
|||
el: any,
|
||||
key: string,
|
||||
value: any,
|
||||
// the following args are passed only due to potential innerHTML/textContent
|
||||
// overriding existing VNodes, in which case the old tree must be properly
|
||||
// unmounted.
|
||||
prevChildren: any,
|
||||
parentComponent: any,
|
||||
parentSuspense: any,
|
||||
unmountChildren: any,
|
||||
) {
|
||||
if (key === 'innerHTML' || key === 'textContent') {
|
||||
if (prevChildren) {
|
||||
unmountChildren(prevChildren, parentComponent, parentSuspense)
|
||||
}
|
||||
el[key] = value == null ? '' : value
|
||||
// null value case is handled in renderer patchElement before patching
|
||||
// children
|
||||
if (value === null) return
|
||||
el[key] = value
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -21,10 +21,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
prevValue,
|
||||
nextValue,
|
||||
namespace,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
) => {
|
||||
const isSVG = namespace === 'svg'
|
||||
if (key === 'class') {
|
||||
|
@ -43,15 +40,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
? ((key = key.slice(1)), false)
|
||||
: shouldSetAsProp(el, key, nextValue, isSVG)
|
||||
) {
|
||||
patchDOMProp(
|
||||
el,
|
||||
key,
|
||||
nextValue,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
patchDOMProp(el, key, nextValue, parentComponent)
|
||||
// #6007 also set form state as attributes so they work with
|
||||
// <input type="reset"> or libs / extensions that expect attributes
|
||||
// #11163 custom elements may use value as an prop and set it as object
|
||||
|
|
Loading…
Reference in New Issue