diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index fe267f4fef6..c371542607d 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -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)) { diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 088d1565cbb..db674f987d8 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -107,10 +107,7 @@ export interface RendererOptions< prevValue: any, nextValue: any, namespace?: ElementNamespace, - prevChildren?: VNode[], 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) { diff --git a/packages/runtime-dom/__tests__/patchProps.spec.ts b/packages/runtime-dom/__tests__/patchProps.spec.ts index 61dd98513ce..7f418847f5f 100644 --- a/packages/runtime-dom/__tests__/patchProps.spec.ts +++ b/packages/runtime-dom/__tests__/patchProps.spec.ts @@ -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(`
baz
`) + state.value = true + await nextTick() + expect(root.innerHTML).toBe(`
baz
`) + }) + test('textContent unmount prev children', () => { const fn = vi.fn() const comp = { diff --git a/packages/runtime-dom/src/modules/props.ts b/packages/runtime-dom/src/modules/props.ts index 2eb83ea39f7..04f0d0e866d 100644 --- a/packages/runtime-dom/src/modules/props.ts +++ b/packages/runtime-dom/src/modules/props.ts @@ -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 } diff --git a/packages/runtime-dom/src/patchProp.ts b/packages/runtime-dom/src/patchProp.ts index e7b733c74af..f3ef14ee83c 100644 --- a/packages/runtime-dom/src/patchProp.ts +++ b/packages/runtime-dom/src/patchProp.ts @@ -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 // or libs / extensions that expect attributes // #11163 custom elements may use value as an prop and set it as object