mirror of
https://github.com/element-plus/element-plus.git
synced 2024-11-27 02:01:15 +08:00
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:
parent
825ce7e0b8
commit
1621b6a2d5
@ -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`
|
||||
|
||||
:::
|
||||
|
||||
|
58
packages/hooks/__tests__/use-z-index.test.tsx
Normal file
58
packages/hooks/__tests__/use-z-index.test.tsx
Normal 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)
|
||||
})
|
||||
})
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user