fix(hydration): do not warn against bindings w/ object values

This commit is contained in:
Evan You 2024-01-11 16:18:00 +08:00
parent 8d656ce88d
commit dcc68ef7d4
2 changed files with 48 additions and 25 deletions

View File

@ -1080,13 +1080,11 @@ describe('SSR hydration', () => {
}) })
test('force hydrate prop with `.prop` modifier', () => { test('force hydrate prop with `.prop` modifier', () => {
const { container } = mountWithHydration( const { container } = mountWithHydration('<input type="checkbox">', () =>
'<input type="checkbox" :indeterminate.prop="true">', h('input', {
() => type: 'checkbox',
h('input', { '.indeterminate': true,
type: 'checkbox', }),
'.indeterminate': true,
}),
) )
expect((container.firstChild! as any).indeterminate).toBe(true) expect((container.firstChild! as any).indeterminate).toBe(true)
}) })
@ -1475,6 +1473,16 @@ describe('SSR hydration', () => {
mountWithHydration(`<select multiple></div>`, () => mountWithHydration(`<select multiple></div>`, () =>
h('select', { multiple: 'multiple' }), h('select', { multiple: 'multiple' }),
) )
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(1)
mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
})
test('attr special case: textarea value', () => {
mountWithHydration(`<textarea>foo</textarea>`, () => mountWithHydration(`<textarea>foo</textarea>`, () =>
h('textarea', { value: 'foo' }), h('textarea', { value: 'foo' }),
) )
@ -1483,11 +1491,10 @@ describe('SSR hydration', () => {
) )
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' })) mountWithHydration(`<textarea>foo</textarea>`, () =>
h('textarea', { value: 'bar' }),
)
expect(`Hydration attribute mismatch`).toHaveBeenWarned() expect(`Hydration attribute mismatch`).toHaveBeenWarned()
mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
}) })
test('boolean attr handling', () => { test('boolean attr handling', () => {
@ -1504,5 +1511,10 @@ describe('SSR hydration', () => {
) )
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
}) })
test('should not warn against object values', () => {
mountWithHydration(`<input />`, () => h('input', { from: {} }))
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
})
}) })
}) })

View File

@ -21,6 +21,7 @@ import {
isBooleanAttr, isBooleanAttr,
isKnownHtmlAttr, isKnownHtmlAttr,
isKnownSvgAttr, isKnownSvgAttr,
isObject,
isOn, isOn,
isReservedProp, isReservedProp,
isString, isString,
@ -759,12 +760,17 @@ function propHasMismatch(
expected = includeBooleanAttr(clientValue) expected = includeBooleanAttr(clientValue)
} else { } else {
// #10000 some attrs such as textarea.value can't be get by `hasAttribute` // #10000 some attrs such as textarea.value can't be get by `hasAttribute`
actual = el.hasAttribute(key) if (el.hasAttribute(key)) {
? el.getAttribute(key) actual = el.getAttribute(key)
: key in el } else if (key in el) {
? el[key as keyof typeof el] const serverValue = el[key as keyof typeof el]
: '' if (!isObject(serverValue)) {
expected = clientValue == null ? '' : String(clientValue) actual = serverValue == null ? '' : String(serverValue)
}
}
if (!isObject(clientValue)) {
expected = clientValue == null ? '' : String(clientValue)
}
} }
if (actual !== expected) { if (actual !== expected) {
mismatchType = `attribute` mismatchType = `attribute`
@ -775,15 +781,20 @@ function propHasMismatch(
if (mismatchType) { if (mismatchType) {
const format = (v: any) => const format = (v: any) =>
v === false ? `(not rendered)` : `${mismatchKey}="${v}"` v === false ? `(not rendered)` : `${mismatchKey}="${v}"`
warn( const preSegment = `Hydration ${mismatchType} mismatch on`
`Hydration ${mismatchType} mismatch on`, const postSegment =
el,
`\n - rendered on server: ${format(actual)}` + `\n - rendered on server: ${format(actual)}` +
`\n - expected on client: ${format(expected)}` + `\n - expected on client: ${format(expected)}` +
`\n Note: this mismatch is check-only. The DOM will not be rectified ` + `\n Note: this mismatch is check-only. The DOM will not be rectified ` +
`in production due to performance overhead.` + `in production due to performance overhead.` +
`\n You should fix the source of the mismatch.`, `\n You should fix the source of the mismatch.`
) if (__TEST__) {
// during tests, log the full message in one single string for easier
// debugging.
warn(`${preSegment} ${el.tagName}${postSegment}`)
} else {
warn(preSegment, el, postSegment)
}
return true return true
} }
return false return false