Skip to content

Commit e496228

Browse files
committed
refactor: style / class mismatch handling
1 parent aec4002 commit e496228

File tree

2 files changed

+96
-89
lines changed

2 files changed

+96
-89
lines changed

packages/runtime-core/src/hydration.ts

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -898,36 +898,38 @@ function propHasMismatch(
898898
}
899899
}
900900

901-
if (mismatchType != null && !isMismatchAllowed(el, mismatchType)) {
902-
warnPropMismatch(el, mismatchKey, mismatchType, actual, expected)
903-
return true
904-
}
905-
return false
901+
return warnPropMismatch(el, mismatchKey, mismatchType, actual, expected)
906902
}
907903

908904
export function warnPropMismatch(
909905
el: Element & { $cls?: string },
910906
mismatchKey: string | undefined,
911-
mismatchType: MismatchTypes,
907+
mismatchType: MismatchTypes | undefined,
912908
actual: string | boolean | null | undefined,
913909
expected: string | boolean | null | undefined,
914-
): void {
915-
const format = (v: any) =>
916-
v === false ? `(not rendered)` : `${mismatchKey}="${v}"`
917-
const preSegment = `Hydration ${MismatchTypeString[mismatchType]} mismatch on`
918-
const postSegment =
919-
`\n - rendered on server: ${format(actual)}` +
920-
`\n - expected on client: ${format(expected)}` +
921-
`\n Note: this mismatch is check-only. The DOM will not be rectified ` +
922-
`in production due to performance overhead.` +
923-
`\n You should fix the source of the mismatch.`
924-
if (__TEST__) {
925-
// during tests, log the full message in one single string for easier
926-
// debugging.
927-
warn(`${preSegment} ${el.tagName}${postSegment}`)
928-
} else {
929-
warn(preSegment, el, postSegment)
910+
): boolean {
911+
if (mismatchType != null && !isMismatchAllowed(el, mismatchType)) {
912+
const format = (v: any) =>
913+
v === false ? `(not rendered)` : `${mismatchKey}="${v}"`
914+
const preSegment = `Hydration ${MismatchTypeString[mismatchType]} mismatch on`
915+
const postSegment =
916+
`\n - rendered on server: ${format(actual)}` +
917+
`\n - expected on client: ${format(expected)}` +
918+
`\n Note: this mismatch is check-only. The DOM will not be rectified ` +
919+
`in production due to performance overhead.` +
920+
`\n You should fix the source of the mismatch.`
921+
if (__TEST__) {
922+
// during tests, log the full message in one single string for easier
923+
// debugging.
924+
warn(`${preSegment} ${el.tagName}${postSegment}`)
925+
} else {
926+
warn(preSegment, el, postSegment)
927+
}
928+
929+
return true
930930
}
931+
932+
return false
931933
}
932934

933935
export function toClassSet(str: string): Set<string> {

packages/runtime-vapor/src/dom/prop.ts

Lines changed: 72 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -127,17 +127,8 @@ export function setClass(el: TargetElement, value: any): void {
127127
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
128128
isHydrating
129129
) {
130-
const actual = el.getAttribute('class')
131-
const actualClassSet = toClassSet(actual || '')
132130
const expected = normalizeClass(value)
133-
const expectedClassSet = toClassSet(expected)
134-
if (!isSetEqual(actualClassSet, expectedClassSet)) {
135-
warnPropMismatch(el, 'class', MismatchTypes.CLASS, actual, expected)
136-
logMismatchError()
137-
el.className = expected
138-
}
139-
140-
el.$cls = expected
131+
handleClassHydration(el, value, expected, false, '$cls')
141132
return
142133
}
143134

@@ -151,26 +142,8 @@ function setClassIncremental(el: any, value: any): void {
151142
const cacheKey = `$clsi${isApplyingFallthroughProps ? '$' : ''}`
152143

153144
if ((__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) && isHydrating) {
154-
const actual = el.getAttribute('class')
155-
const actualClassSet = toClassSet(actual || '')
156145
const expected = normalizeClass(value)
157-
const expectedClassSet = toClassSet(expected)
158-
// check if the expected classes are present in the actual classes
159-
const hasMismatch = Array.from(expectedClassSet).some(
160-
cls => !actualClassSet.has(cls),
161-
)
162-
if (hasMismatch) {
163-
warnPropMismatch(el, 'class', MismatchTypes.CLASS, actual, expected)
164-
logMismatchError()
165-
166-
const nextList = value.split(/\s+/)
167-
if (value) {
168-
el.classList.add(...nextList)
169-
}
170-
} else {
171-
el[cacheKey] = expected
172-
}
173-
146+
handleClassHydration(el, value, expected, true, cacheKey)
174147
return
175148
}
176149

@@ -196,24 +169,8 @@ export function setStyle(el: TargetElement, value: any): void {
196169
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
197170
isHydrating
198171
) {
199-
const actual = el.getAttribute('style')
200-
const actualStyleMap = toStyleMap(actual || '')
201172
const normalizedValue = normalizeStyle(value)
202-
const expected = stringifyStyle(normalizedValue)
203-
const expectedStyleMap = toStyleMap(expected)
204-
205-
// If `v-show=false`, `display: 'none'` should be added to expected
206-
if ((el as any)[vShowHidden]) {
207-
expectedStyleMap.set('display', 'none')
208-
}
209-
210-
// TODO: handle css vars
211-
212-
if (!isMapEqual(actualStyleMap, expectedStyleMap)) {
213-
warnPropMismatch(el, 'style', MismatchTypes.STYLE, actual, expected)
214-
logMismatchError()
215-
patchStyle(el, el.$sty, (el.$sty = normalizedValue))
216-
}
173+
handleStyleHydration(el, value, normalizedValue, false, '$sty')
217174
return
218175
}
219176

@@ -228,27 +185,7 @@ function setStyleIncremental(el: any, value: any): NormalizedStyle | undefined {
228185
: (normalizeStyle(value) as NormalizedStyle | undefined)
229186

230187
if ((__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) && isHydrating) {
231-
const actual = el.getAttribute('style')
232-
const actualStyleMap = toStyleMap(actual || '')
233-
const expected = isString(value) ? value : stringifyStyle(normalizedValue)
234-
const expectedStyleMap = toStyleMap(expected)
235-
236-
// If `v-show=false`, `display: 'none'` should be added to expected
237-
if (el[vShowHidden]) {
238-
expectedStyleMap.set('display', 'none')
239-
}
240-
241-
// TODO: handle css vars
242-
243-
// check if the expected styles are present in the actual styles
244-
const hasMismatch = Array.from(expectedStyleMap.entries()).some(
245-
([key, val]) => actualStyleMap.get(key) !== val,
246-
)
247-
if (hasMismatch) {
248-
warnPropMismatch(el, 'style', MismatchTypes.STYLE, actual, expected)
249-
logMismatchError()
250-
patchStyle(el, el[cacheKey], (el[cacheKey] = normalizedValue))
251-
}
188+
handleStyleHydration(el, value, normalizedValue, true, cacheKey)
252189
return
253190
}
254191

@@ -446,3 +383,71 @@ export function optimizePropertyLookup(): void {
446383
(Text.prototype as any).$txt =
447384
''
448385
}
386+
387+
function handleClassHydration(
388+
el: TargetElement | any,
389+
value: any,
390+
expected: string,
391+
isIncremental: boolean,
392+
cacheKey: string,
393+
) {
394+
const actual = el.getAttribute('class')
395+
const actualClassSet = toClassSet(actual || '')
396+
const expectedClassSet = toClassSet(expected)
397+
398+
const hasMismatch = isIncremental
399+
? // check if the expected classes are present in the actual classes
400+
Array.from(expectedClassSet).some(cls => !actualClassSet.has(cls))
401+
: !isSetEqual(actualClassSet, expectedClassSet)
402+
403+
if (hasMismatch) {
404+
warnPropMismatch(el, 'class', MismatchTypes.CLASS, actual, expected)
405+
logMismatchError()
406+
407+
if (isIncremental) {
408+
const nextList = value.split(/\s+/)
409+
if (value) {
410+
el.classList.add(...nextList)
411+
}
412+
} else {
413+
el.className = expected
414+
}
415+
}
416+
417+
el[cacheKey] = expected
418+
}
419+
420+
function handleStyleHydration(
421+
el: TargetElement | any,
422+
value: any,
423+
normalizedValue: string | NormalizedStyle | undefined,
424+
isIncremental: boolean,
425+
cacheKey: string,
426+
) {
427+
const actual = el.getAttribute('style')
428+
const actualStyleMap = toStyleMap(actual || '')
429+
const expected = isString(value) ? value : stringifyStyle(normalizedValue)
430+
const expectedStyleMap = toStyleMap(expected)
431+
432+
// If `v-show=false`, `display: 'none'` should be added to expected
433+
if (el[vShowHidden]) {
434+
expectedStyleMap.set('display', 'none')
435+
}
436+
437+
// TODO: handle css vars
438+
439+
const hasMismatch = isIncremental
440+
? // check if the expected styles are present in the actual styles
441+
Array.from(expectedStyleMap.entries()).some(
442+
([key, val]) => actualStyleMap.get(key) !== val,
443+
)
444+
: !isMapEqual(actualStyleMap, expectedStyleMap)
445+
446+
if (hasMismatch) {
447+
warnPropMismatch(el, 'style', MismatchTypes.STYLE, actual, expected)
448+
logMismatchError()
449+
patchStyle(el, el[cacheKey], (el[cacheKey] = normalizedValue))
450+
}
451+
452+
el[cacheKey] = normalizedValue
453+
}

0 commit comments

Comments
 (0)