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
|
// force hydrate v-bind with .prop modifiers
|
||||||
key[0] === '.'
|
key[0] === '.'
|
||||||
) {
|
) {
|
||||||
patchProp(
|
patchProp(el, key, null, props[key], undefined, parentComponent)
|
||||||
el,
|
|
||||||
key,
|
|
||||||
null,
|
|
||||||
props[key],
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
parentComponent,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (props.onClick) {
|
} else if (props.onClick) {
|
||||||
|
@ -485,7 +477,6 @@ export function createHydrationFunctions(
|
||||||
null,
|
null,
|
||||||
props.onClick,
|
props.onClick,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
|
||||||
parentComponent,
|
parentComponent,
|
||||||
)
|
)
|
||||||
} else if (patchFlag & PatchFlags.STYLE && isReactive(props.style)) {
|
} else if (patchFlag & PatchFlags.STYLE && isReactive(props.style)) {
|
||||||
|
|
|
@ -107,10 +107,7 @@ export interface RendererOptions<
|
||||||
prevValue: any,
|
prevValue: any,
|
||||||
nextValue: any,
|
nextValue: any,
|
||||||
namespace?: ElementNamespace,
|
namespace?: ElementNamespace,
|
||||||
prevChildren?: VNode<HostNode, HostElement>[],
|
|
||||||
parentComponent?: ComponentInternalInstance | null,
|
parentComponent?: ComponentInternalInstance | null,
|
||||||
parentSuspense?: SuspenseBoundary | null,
|
|
||||||
unmountChildren?: UnmountChildrenFn,
|
|
||||||
): void
|
): void
|
||||||
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
|
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
|
||||||
remove(el: HostNode): void
|
remove(el: HostNode): void
|
||||||
|
@ -670,17 +667,7 @@ function baseCreateRenderer(
|
||||||
if (props) {
|
if (props) {
|
||||||
for (const key in props) {
|
for (const key in props) {
|
||||||
if (key !== 'value' && !isReservedProp(key)) {
|
if (key !== 'value' && !isReservedProp(key)) {
|
||||||
hostPatchProp(
|
hostPatchProp(el, key, null, props[key], namespace, parentComponent)
|
||||||
el,
|
|
||||||
key,
|
|
||||||
null,
|
|
||||||
props[key],
|
|
||||||
namespace,
|
|
||||||
vnode.children as VNode[],
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
unmountChildren,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -833,6 +820,15 @@ function baseCreateRenderer(
|
||||||
dynamicChildren = null
|
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) {
|
if (dynamicChildren) {
|
||||||
patchBlockChildren(
|
patchBlockChildren(
|
||||||
n1.dynamicChildren!,
|
n1.dynamicChildren!,
|
||||||
|
@ -869,15 +865,7 @@ function baseCreateRenderer(
|
||||||
// (i.e. at the exact same position in the source template)
|
// (i.e. at the exact same position in the source template)
|
||||||
if (patchFlag & PatchFlags.FULL_PROPS) {
|
if (patchFlag & PatchFlags.FULL_PROPS) {
|
||||||
// element props contain dynamic keys, full diff needed
|
// element props contain dynamic keys, full diff needed
|
||||||
patchProps(
|
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||||
el,
|
|
||||||
n2,
|
|
||||||
oldProps,
|
|
||||||
newProps,
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
namespace,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
// class
|
// class
|
||||||
// this flag is matched when the element has dynamic class bindings.
|
// this flag is matched when the element has dynamic class bindings.
|
||||||
|
@ -908,17 +896,7 @@ function baseCreateRenderer(
|
||||||
const next = newProps[key]
|
const next = newProps[key]
|
||||||
// #1471 force patch value
|
// #1471 force patch value
|
||||||
if (next !== prev || key === 'value') {
|
if (next !== prev || key === 'value') {
|
||||||
hostPatchProp(
|
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||||
el,
|
|
||||||
key,
|
|
||||||
prev,
|
|
||||||
next,
|
|
||||||
namespace,
|
|
||||||
n1.children as VNode[],
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
unmountChildren,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -933,15 +911,7 @@ function baseCreateRenderer(
|
||||||
}
|
}
|
||||||
} else if (!optimized && dynamicChildren == null) {
|
} else if (!optimized && dynamicChildren == null) {
|
||||||
// unoptimized, full diff
|
// unoptimized, full diff
|
||||||
patchProps(
|
patchProps(el, oldProps, newProps, parentComponent, namespace)
|
||||||
el,
|
|
||||||
n2,
|
|
||||||
oldProps,
|
|
||||||
newProps,
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
namespace,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
|
if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
|
||||||
|
@ -998,11 +968,9 @@ function baseCreateRenderer(
|
||||||
|
|
||||||
const patchProps = (
|
const patchProps = (
|
||||||
el: RendererElement,
|
el: RendererElement,
|
||||||
vnode: VNode,
|
|
||||||
oldProps: Data,
|
oldProps: Data,
|
||||||
newProps: Data,
|
newProps: Data,
|
||||||
parentComponent: ComponentInternalInstance | null,
|
parentComponent: ComponentInternalInstance | null,
|
||||||
parentSuspense: SuspenseBoundary | null,
|
|
||||||
namespace: ElementNamespace,
|
namespace: ElementNamespace,
|
||||||
) => {
|
) => {
|
||||||
if (oldProps !== newProps) {
|
if (oldProps !== newProps) {
|
||||||
|
@ -1015,10 +983,7 @@ function baseCreateRenderer(
|
||||||
oldProps[key],
|
oldProps[key],
|
||||||
null,
|
null,
|
||||||
namespace,
|
namespace,
|
||||||
vnode.children as VNode[],
|
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
|
||||||
unmountChildren,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1030,17 +995,7 @@ function baseCreateRenderer(
|
||||||
const prev = oldProps[key]
|
const prev = oldProps[key]
|
||||||
// defer patching value
|
// defer patching value
|
||||||
if (next !== prev && key !== 'value') {
|
if (next !== prev && key !== 'value') {
|
||||||
hostPatchProp(
|
hostPatchProp(el, key, prev, next, namespace, parentComponent)
|
||||||
el,
|
|
||||||
key,
|
|
||||||
prev,
|
|
||||||
next,
|
|
||||||
namespace,
|
|
||||||
vnode.children as VNode[],
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
unmountChildren,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ('value' in newProps) {
|
if ('value' in newProps) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { patchProp } from '../src/patchProp'
|
import { patchProp } from '../src/patchProp'
|
||||||
import { h, render } from '../src'
|
import { h, nextTick, ref, render } from '../src'
|
||||||
|
|
||||||
describe('runtime-dom: props patching', () => {
|
describe('runtime-dom: props patching', () => {
|
||||||
test('basic', () => {
|
test('basic', () => {
|
||||||
|
@ -133,6 +133,25 @@ describe('runtime-dom: props patching', () => {
|
||||||
expect(fn).toHaveBeenCalled()
|
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', () => {
|
test('textContent unmount prev children', () => {
|
||||||
const fn = vi.fn()
|
const fn = vi.fn()
|
||||||
const comp = {
|
const comp = {
|
||||||
|
|
|
@ -10,19 +10,13 @@ export function patchDOMProp(
|
||||||
el: any,
|
el: any,
|
||||||
key: string,
|
key: string,
|
||||||
value: any,
|
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,
|
parentComponent: any,
|
||||||
parentSuspense: any,
|
|
||||||
unmountChildren: any,
|
|
||||||
) {
|
) {
|
||||||
if (key === 'innerHTML' || key === 'textContent') {
|
if (key === 'innerHTML' || key === 'textContent') {
|
||||||
if (prevChildren) {
|
// null value case is handled in renderer patchElement before patching
|
||||||
unmountChildren(prevChildren, parentComponent, parentSuspense)
|
// children
|
||||||
}
|
if (value === null) return
|
||||||
el[key] = value == null ? '' : value
|
el[key] = value
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
||||||
prevValue,
|
prevValue,
|
||||||
nextValue,
|
nextValue,
|
||||||
namespace,
|
namespace,
|
||||||
prevChildren,
|
|
||||||
parentComponent,
|
parentComponent,
|
||||||
parentSuspense,
|
|
||||||
unmountChildren,
|
|
||||||
) => {
|
) => {
|
||||||
const isSVG = namespace === 'svg'
|
const isSVG = namespace === 'svg'
|
||||||
if (key === 'class') {
|
if (key === 'class') {
|
||||||
|
@ -43,15 +40,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
||||||
? ((key = key.slice(1)), false)
|
? ((key = key.slice(1)), false)
|
||||||
: shouldSetAsProp(el, key, nextValue, isSVG)
|
: shouldSetAsProp(el, key, nextValue, isSVG)
|
||||||
) {
|
) {
|
||||||
patchDOMProp(
|
patchDOMProp(el, key, nextValue, parentComponent)
|
||||||
el,
|
|
||||||
key,
|
|
||||||
nextValue,
|
|
||||||
prevChildren,
|
|
||||||
parentComponent,
|
|
||||||
parentSuspense,
|
|
||||||
unmountChildren,
|
|
||||||
)
|
|
||||||
// #6007 also set form state as attributes so they work with
|
// #6007 also set form state as attributes so they work with
|
||||||
// <input type="reset"> or libs / extensions that expect attributes
|
// <input type="reset"> or libs / extensions that expect attributes
|
||||||
// #11163 custom elements may use value as an prop and set it as object
|
// #11163 custom elements may use value as an prop and set it as object
|
||||||
|
|
Loading…
Reference in New Issue