fix(hooks): SSR hydration error caused by z-index (#16175)

* fix(hooks): SSR hydration error caused by z-index

* test(hooks): test error

* chore: optimize name

* update

Co-authored-by: btea <2356281422@qq.com>

---------

Co-authored-by: btea <2356281422@qq.com>
This commit is contained in:
qiang 2024-03-22 16:36:25 +08:00 committed by GitHub
parent 825ce7e0b8
commit 1621b6a2d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 103 additions and 3 deletions

View File

@ -31,6 +31,20 @@ app.provide(ID_INJECTION_KEY, {
})
```
## Provide ZIndex
When you using SSR for development, you may encounter hydration errors caused by `z-index`. In this case, we recommend injecting an initial value to avoid such errors.
```ts
// src/main.js (irrelevant code omitted)
import { createApp } from 'vue'
import { ZINDEX_INJECTION_KEY } from 'element-plus'
import App from './App.vue'
const app = createApp(App)
app.provide(ZINDEX_INJECTION_KEY, { current: 0 })
```
## Teleports
[Teleport](https://vuejs.org/guide/scaling-up/ssr.html#teleports) is used internally by multiple components in Element Plus (eg. ElDialog, ElDrawer, ElTooltip, ElDropdown, ElSelect, ElDatePicker ...), so special handling is required during SSR.
@ -79,7 +93,7 @@ There may be some [SSR problems with teleport](https://github.com/vuejs/core/iss
1. The `teleported` attribute in all components based on ElTooltip should be consistent, it is recommended to use the default value.
2. The `append-to-body` attribute value of ElDialog and ElDrawer should be consistent, it is recommended to enable the `append-to-body`.
3. When the ElSubMenu component has a multi-layer popup, It is recommended to enable the `popper-append-to-body`
3. When the ElSubMenu component has a multi-layer popup, It is recommended to enable the `teleported`
:::

View File

@ -0,0 +1,58 @@
import { config, mount } from '@vue/test-utils'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { ZINDEX_INJECTION_KEY, useZIndex } from '../use-z-index'
describe('no injection value', () => {
afterEach(() => {
document.body.innerHTML = ''
})
it('useZIndex', () => {
const wrapper = mount({
setup() {
const { initialZIndex, currentZIndex, nextZIndex } = useZIndex()
return { initialZIndex, currentZIndex, nextZIndex: nextZIndex() }
},
render: () => undefined,
})
expect(wrapper.vm.initialZIndex).toBe(2000)
expect(wrapper.vm.currentZIndex).toBe(2001)
expect(wrapper.vm.nextZIndex).toBe(2001)
})
})
describe('with injection value', () => {
beforeEach(() => {
config.global.provide = {
[ZINDEX_INJECTION_KEY as symbol]: {
current: 10,
},
}
})
afterEach(() => {
document.body.innerHTML = ''
config.global.provide = {}
})
it('useZIndex', () => {
const wrapper = mount({
setup() {
const { initialZIndex, currentZIndex, nextZIndex } = useZIndex()
nextZIndex()
return {
initialZIndex,
currentZIndex,
nextZIndex: nextZIndex(),
}
},
render: () => undefined,
})
expect(wrapper.vm.initialZIndex).toBe(2000)
expect(wrapper.vm.currentZIndex).toBe(2012)
expect(wrapper.vm.nextZIndex).toBe(2012)
})
})

View File

@ -1,31 +1,59 @@
import { computed, getCurrentInstance, inject, ref, unref } from 'vue'
import { isNumber } from '@element-plus/utils'
import { debugWarn, isClient, isNumber } from '@element-plus/utils'
import type { InjectionKey, Ref } from 'vue'
export interface ElZIndexInjectionContext {
current: number
}
const initial: ElZIndexInjectionContext = {
current: 0,
}
const zIndex = ref(0)
export const defaultInitialZIndex = 2000
// For SSR
export const ZINDEX_INJECTION_KEY: InjectionKey<ElZIndexInjectionContext> =
Symbol('elZIndexContextKey')
export const zIndexContextKey: InjectionKey<Ref<number | undefined>> =
Symbol('zIndexContextKey')
export const useZIndex = (zIndexOverrides?: Ref<number>) => {
const increasingInjection = getCurrentInstance()
? inject(ZINDEX_INJECTION_KEY, initial)
: initial
const zIndexInjection =
zIndexOverrides ||
(getCurrentInstance() ? inject(zIndexContextKey, undefined) : undefined)
const initialZIndex = computed(() => {
const zIndexFromInjection = unref(zIndexInjection)
return isNumber(zIndexFromInjection)
? zIndexFromInjection
: defaultInitialZIndex
})
const currentZIndex = computed(() => initialZIndex.value + zIndex.value)
const nextZIndex = () => {
zIndex.value++
increasingInjection.current++
zIndex.value = increasingInjection.current
return currentZIndex.value
}
if (!isClient && !inject(ZINDEX_INJECTION_KEY)) {
debugWarn(
'ZIndexInjection',
`Looks like you are using server rendering, you must provide a z-index provider to ensure the hydration process to be succeed
usage: app.provide(ZINDEX_INJECTION_KEY, { current: 0 })`
)
}
return {
initialZIndex,
currentZIndex,