mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-11-29 17:58:08 +08:00
feat(components): [skeleton] throttle
supports values as object (#17041)
* fix(components): [skeleton] `throttle` property not working * fix: lint fix * feat: add func & doc & test * feat: remove test modify * feat: increase document examples, improve document descriptions * fix(components): [skeleton] `throttle` property not working * fix: lint fix * feat: add func & doc & test * feat: remove test modify * feat: increase document examples, improve document descriptions * feat: 重构`useThrottleRender`钩子以提高代码可读性和效率 - 简化了对`throttle`参数的判断逻辑,通过`isNumber`函数判断是否为数字 - 将`leadingDispatch`和`trailingDispatch`函数合并为`dispatcher`函数,根据传入的类型判断执行逻辑 - 优化了`watch`回调函数,使用`dispatcher`函数替代重复的判断逻辑 * feat: 写法优化 * feat: 引入`isObject`函数替代原有的`typeof throttle === 'object'`判断方式 * feat: 优化骨架屏文档结构和示例 * feat: 完善文字描述和修改对应的文件名 * Update docs/en-US/component/skeleton.md Co-authored-by: btea <2356281422@qq.com> * Update docs/en-US/component/skeleton.md Co-authored-by: btea <2356281422@qq.com> * feat: Optimize code writing * Update docs/en-US/component/skeleton.md Co-authored-by: btea <2356281422@qq.com> * Update docs/en-US/component/skeleton.md * feat: modify doc * feat: md * feat: 补充 useThrottleRender 钩子的测试用例 * feat: 将 SkeletonThrottle 类型移动到hook中, 重命名为 ThrottleType 以提高通用性 --------- Co-authored-by: btea <2356281422@qq.com>
This commit is contained in:
parent
89731b7d1f
commit
3eb734ccc4
@ -79,23 +79,57 @@ skeleton/rendering-with-data
|
||||
|
||||
Sometimes API responds very quickly, when that happens, the skeleton just gets rendered to the DOM then it needs to switch back to real DOM, that causes the sudden flashy. To avoid such thing, you can use the `throttle` attribute.
|
||||
|
||||
:::tip
|
||||
|
||||
Since ^(2.8.8), the `throttle` attribute supports two values: `number` and `object`. When passing a `number`, it is equivalent to `{leading: xxx}`, controlling the throttling of the skeleton screen display. Of course, you can also control the throttling of the skeleton screen disappearance by passing `{trailing: xxx}`
|
||||
|
||||
:::
|
||||
|
||||
:::demo
|
||||
|
||||
skeleton/avoiding-rendering-bouncing
|
||||
|
||||
:::
|
||||
|
||||
## Initial rendering loading ^(2.8.8)
|
||||
|
||||
When the initial value of loading is true, you can set `throttle: {initVal: true, leading: xxx}` to control the immediate display of the initial skeleton screen without throttling.
|
||||
|
||||
:::demo
|
||||
|
||||
skeleton/initial-rendering-loading
|
||||
|
||||
:::
|
||||
|
||||
## Toggle show/hide without rending bouncing ^(2.8.8)
|
||||
|
||||
:::tip
|
||||
|
||||
You can set `throttle: {initVal: true, leading: xxx, trailing: xxx}` to control the initial display of the skeleton effect and to make the transition of the skeleton more smooth when switching loading states.
|
||||
|
||||
:::
|
||||
|
||||
Sometimes you want to render the business components more smoothly when loading toggle show or hide. You can use set the `throttle: {leading: xxx, trailing:xxx}` to control the rendering bouncing.
|
||||
|
||||
:::demo
|
||||
|
||||
skeleton/leading-trailing-without-bouncing
|
||||
|
||||
:::
|
||||
|
||||
##
|
||||
|
||||
## Skeleton API
|
||||
|
||||
### Skeleton Attributes
|
||||
|
||||
| Name | Description | Type | Default |
|
||||
| -------- | ---------------------------------------------------------------- | ---------- | ------- |
|
||||
| animated | whether showing the animation | ^[boolean] | false |
|
||||
| count | how many fake items to render to the DOM | ^[number] | 1 |
|
||||
| loading | whether showing the real DOM | ^[boolean] | false |
|
||||
| rows | numbers of the row, only useful when no template slot were given | ^[number] | 3 |
|
||||
| throttle | rendering delay in milliseconds | ^[number] | 0 |
|
||||
| Name | Description | Type | Default |
|
||||
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------- |
|
||||
| animated | whether showing the animation | ^[boolean] | false |
|
||||
| count | how many fake items to render to the DOM | ^[number] | 1 |
|
||||
| loading | whether showing the real DOM | ^[boolean] | false |
|
||||
| rows | numbers of the row, only useful when no template slot were given | ^[number] | 3 |
|
||||
| throttle | rendering delay in milliseconds. Numbers represent delayed display, and can also be set to delay hide, for example `{ leading: 500, trailing: 500 }`. When needing to control the initial value of loading, you can set `{ initVal: true }` | ^[number] / ^[object]`{ leading?: number, trailing?: number, initVal?: boolean }` | 0 |
|
||||
|
||||
### Skeleton Slots
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
:throttle="500"
|
||||
>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="image" style="width: 240px; height: 240px" />
|
||||
<el-skeleton-item variant="image" style="width: 240px; height: 265px" />
|
||||
<div style="padding: 14px">
|
||||
<el-skeleton-item variant="h3" style="width: 50%" />
|
||||
<div
|
||||
|
55
docs/examples/skeleton/initial-rendering-loading.vue
Normal file
55
docs/examples/skeleton/initial-rendering-loading.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<el-space direction="vertical" alignment="flex-start">
|
||||
<div>
|
||||
<label style="margin-right: 16px">Switch Loading</label>
|
||||
<el-switch v-model="loading" />
|
||||
</div>
|
||||
<el-skeleton
|
||||
style="width: 240px"
|
||||
:loading="loading"
|
||||
animated
|
||||
:throttle="{ leading: 500, initVal: true }"
|
||||
>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="image" style="width: 240px; height: 265px" />
|
||||
<div style="padding: 14px">
|
||||
<el-skeleton-item variant="h3" style="width: 50%" />
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: space-between;
|
||||
margin-top: 16px;
|
||||
height: 16px;
|
||||
"
|
||||
>
|
||||
<el-skeleton-item variant="text" style="margin-right: 16px" />
|
||||
<el-skeleton-item variant="text" style="width: 30%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
|
||||
<img
|
||||
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
|
||||
class="image"
|
||||
/>
|
||||
<div style="padding: 14px">
|
||||
<span>Delicious hamburger</span>
|
||||
<div class="bottom card-header">
|
||||
<div class="time">{{ currentDate }}</div>
|
||||
<el-button text class="button">operation button</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const loading = ref(true)
|
||||
const currentDate = new Date().toDateString()
|
||||
</script>
|
55
docs/examples/skeleton/leading-trailing-without-bouncing.vue
Normal file
55
docs/examples/skeleton/leading-trailing-without-bouncing.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<el-space direction="vertical" alignment="flex-start">
|
||||
<div>
|
||||
<label style="margin-right: 16px">Switch Loading</label>
|
||||
<el-switch v-model="loading" />
|
||||
</div>
|
||||
<el-skeleton
|
||||
style="width: 240px"
|
||||
:loading="loading"
|
||||
animated
|
||||
:throttle="{ leading: 500, trailing: 500, initVal: true }"
|
||||
>
|
||||
<template #template>
|
||||
<el-skeleton-item variant="image" style="width: 240px; height: 265px" />
|
||||
<div style="padding: 14px">
|
||||
<el-skeleton-item variant="h3" style="width: 50%" />
|
||||
<div
|
||||
style="
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-items: space-between;
|
||||
margin-top: 16px;
|
||||
height: 16px;
|
||||
"
|
||||
>
|
||||
<el-skeleton-item variant="text" style="margin-right: 16px" />
|
||||
<el-skeleton-item variant="text" style="width: 30%" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #default>
|
||||
<el-card :body-style="{ padding: '0px', marginBottom: '1px' }">
|
||||
<img
|
||||
src="https://shadow.elemecdn.com/app/element/hamburger.9cf7b091-55e9-11e9-a976-7f4d0b07eef6.png"
|
||||
class="image"
|
||||
/>
|
||||
<div style="padding: 14px">
|
||||
<span>Delicious hamburger</span>
|
||||
<div class="bottom card-header">
|
||||
<div class="time">{{ currentDate }}</div>
|
||||
<el-button text class="button">operation button</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
</el-skeleton>
|
||||
</el-space>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const currentDate = new Date().toDateString()
|
||||
</script>
|
@ -83,4 +83,22 @@ describe('Skeleton.vue', () => {
|
||||
|
||||
expect((wrapper.vm as SkeletonInstance).uiLoading).toBe(true)
|
||||
})
|
||||
|
||||
it('should throttle object rendering', async () => {
|
||||
const wrapper = mount(
|
||||
<Skeleton throttle={{ trailing: 500, initVal: true }} loading={true} />
|
||||
)
|
||||
|
||||
expect((wrapper.vm as SkeletonInstance).uiLoading).toBe(true)
|
||||
|
||||
await wrapper.setProps({
|
||||
loading: false,
|
||||
})
|
||||
|
||||
vi.runAllTimers()
|
||||
|
||||
await nextTick()
|
||||
|
||||
expect((wrapper.vm as SkeletonInstance).uiLoading).toBe(false)
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { buildProps } from '@element-plus/utils'
|
||||
import { buildProps, definePropType } from '@element-plus/utils'
|
||||
import type Skeleton from './skeleton.vue'
|
||||
import type { ExtractPropTypes } from 'vue'
|
||||
import type { ThrottleType } from '@element-plus/hooks'
|
||||
|
||||
export const skeletonProps = buildProps({
|
||||
/**
|
||||
@ -35,7 +36,7 @@ export const skeletonProps = buildProps({
|
||||
* @description rendering delay in milliseconds
|
||||
*/
|
||||
throttle: {
|
||||
type: Number,
|
||||
type: definePropType<ThrottleType>([Number, Object]),
|
||||
},
|
||||
} as const)
|
||||
export type SkeletonProps = ExtractPropTypes<typeof skeletonProps>
|
||||
|
@ -2,7 +2,7 @@
|
||||
<template v-if="uiLoading">
|
||||
<div :class="[ns.b(), ns.is('animated', animated)]" v-bind="$attrs">
|
||||
<template v-for="i in count" :key="i">
|
||||
<slot v-if="loading" :key="i" name="template">
|
||||
<slot v-if="uiLoading" :key="i" name="template">
|
||||
<el-skeleton-item :class="ns.is('first')" variant="p" />
|
||||
<el-skeleton-item
|
||||
v-for="item in rows"
|
||||
|
@ -47,4 +47,29 @@ describe.concurrent('useThrottleRender', () => {
|
||||
loading.value = true
|
||||
expect(throttled.value).toBe(false) // should still be false after throttle time
|
||||
})
|
||||
|
||||
it('should use `initVal` as initial value when pass `{ initVal: true/false }`', async () => {
|
||||
const loading = ref(false)
|
||||
const throttled = useThrottleRender(loading, { initVal: true })
|
||||
expect(throttled.value).toBe(true)
|
||||
const throttled2 = useThrottleRender(loading, { initVal: false })
|
||||
expect(throttled2.value).toBe(false)
|
||||
})
|
||||
|
||||
it('should throttle on display and disappear when pass `{ leading: xxx, trailing: xxx }`', async () => {
|
||||
const loading = ref(false)
|
||||
const throttled = useThrottleRender(loading, {
|
||||
leading: 200,
|
||||
trailing: 200,
|
||||
})
|
||||
expect(throttled.value).toBe(false) // initially false when not pass initVal
|
||||
loading.value = true
|
||||
expect(throttled.value).toBe(false) // should remain false until throttle time
|
||||
await sleep(250) // Here, the delay time cannot be set to 200, setTimeout is not so precise, you can set it a little larger.
|
||||
expect(throttled.value).toBe(true) // should be true after throttle time
|
||||
loading.value = false
|
||||
expect(throttled.value).toBe(true) // should remain true until throttle time
|
||||
await sleep(250) // Here, the delay time cannot be set to 200, setTimeout is not so precise, you can set it a little larger.
|
||||
expect(throttled.value).toBe(false) // should be false after throttle time
|
||||
})
|
||||
})
|
||||
|
@ -1,31 +1,58 @@
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { isNumber, isObject, isUndefined } from '@element-plus/utils'
|
||||
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
export const useThrottleRender = (loading: Ref<boolean>, throttle = 0) => {
|
||||
export type ThrottleType =
|
||||
| { leading?: number; trailing?: number; initVal?: boolean }
|
||||
| number
|
||||
|
||||
export const useThrottleRender = (
|
||||
loading: Ref<boolean>,
|
||||
throttle: ThrottleType = 0
|
||||
) => {
|
||||
if (throttle === 0) return loading
|
||||
const throttled = ref(false)
|
||||
const initVal = isObject(throttle) && Boolean(throttle.initVal)
|
||||
const throttled = ref(initVal)
|
||||
let timeoutHandle: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const dispatchThrottling = () => {
|
||||
const dispatchThrottling = (timer: number | undefined) => {
|
||||
if (isUndefined(timer)) {
|
||||
throttled.value = loading.value
|
||||
return
|
||||
}
|
||||
if (timeoutHandle) {
|
||||
clearTimeout(timeoutHandle)
|
||||
}
|
||||
timeoutHandle = setTimeout(() => {
|
||||
throttled.value = loading.value
|
||||
}, throttle)
|
||||
}, timer)
|
||||
}
|
||||
onMounted(dispatchThrottling)
|
||||
|
||||
const dispatcher = (type: 'leading' | 'trailing') => {
|
||||
if (type === 'leading') {
|
||||
if (isNumber(throttle)) {
|
||||
dispatchThrottling(throttle)
|
||||
} else {
|
||||
dispatchThrottling(throttle.leading)
|
||||
}
|
||||
} else {
|
||||
if (isObject(throttle)) {
|
||||
dispatchThrottling(throttle.trailing)
|
||||
} else {
|
||||
throttled.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => dispatcher('leading'))
|
||||
|
||||
watch(
|
||||
() => loading.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
dispatchThrottling()
|
||||
} else {
|
||||
throttled.value = val
|
||||
}
|
||||
dispatcher(val ? 'leading' : 'trailing')
|
||||
}
|
||||
)
|
||||
|
||||
return throttled
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user