[vue3] 开发过程中实现的 hooks

394 阅读2分钟

1 动态计算表格组件的高度

1.1 说明

作用:在使用 el-table 或 a-table 时,总是需要固定表头,而固定表头就需要设置高度,高度通常是要撑满容器,但百分比或 calc 设置的高度并不准确。

思路:给表格组件包装一层,通过获取包装容器的高度来设置表格高度。

1.2 实现

/*
 * 表格高度自动适配
 */

import { onMounted, onUnmounted, Ref, ref } from 'vue'
import * as utils from '@/util/common'

export default function useTableHeight(tableWrapperRef: Ref<HTMLElement | undefined>) {
    const tableHeight = ref<number>()

    const setTableHeight = () => {
        if (!tableWrapperRef.value) return
        tableHeight.value = tableWrapperRef.value.clientHeight
    }

    const resizeHandler = utils.throttle(() => {
        setTableHeight()
    }, 300)

    onMounted(() => {
        setTableHeight()
        window.addEventListener('resize', resizeHandler)
    })

    onUnmounted(() => {
        window.removeEventListener('resize', resizeHandler)
    })

    return {
        tableHeight,
        setTableHeight,
    }
}

1.3 使用

<div ref="tableWrapperRef">
    <el-table :height="tableHeight"><a-table :scroll="{ y: tableHeight }">
</div>

const tableWrapperRef = ref()
const { tableHeight, setTableHeight } = useTableHeight(tableWrapperRef)

另外:

  1. 包装容器需要设置 flex: 1,得撑开高度;
  2. 如果表格空白,手动调用 setTableHeight;
  3. a-table 的 scroll.y 设置的是表体高度,计算结果需要减去表头高度才准确。

2 离开表单页面前提示数据会丢失

2.1 说明

作用:让用户清楚的知道,如果离开或刷新表单页面,那么已经填写的内容就会丢失。

思路:监听表单字段,只要发生改变,那么在离开路由或刷新时就触发提示。

特殊场景的处理

场景一:有的表单页面在初始化时会自动给表单项赋值,如果一开始就监听的话,总是会触发提示

解决方法:延迟监听(目前没想到更好的方法)

场景二:表单提交成功跳转时,不能触发提示

解决方法:在跳转前清除监听

2.2 实现

/**
 * 离开或刷新表单页面前提示数据会丢失
 */
 
import { Modal } from 'ant-design-vue'
import { WatchStopHandle, onMounted, onUnmounted, ref, watch } from 'vue'
import { onBeforeRouteLeave } from 'vue-router'

export default function useFormPageLeavePrompt(watchData: any, delay: number = 300) {
    const isFormEdit = ref(false)
    let watchTimer: number
    let watchStop: WatchStopHandle

    // 刷新提示
    const handleBeforeunload = (e: any) => {
        // 标准写法
        e.preventDefault()
        // Chrome
        e.returnValue = ''
    }

    // 离开路由提示
    onBeforeRouteLeave((to, from, next) => {
        if (isFormEdit.value) {
            Modal.destroyAll()
            Modal.confirm({
                title: '提示',
                content: '确定离开吗?表单数据会丢失。',
                class: 'leave-form-modal',
                onOk: () => {
                    next(true)
                },
                onCancel: () => {
                    next(false)
                },
            })
        } else {
            next(true)
        }
    })

    // 监听传入的表单数据
    const watchFormData = () => {
        watchTimer = setTimeout(() => {
            watchStop = watch(
                watchData,
                (newVal, oldVal) => {
                    console.log('表单变了')
                    isFormEdit.value = true
                    window.addEventListener('beforeunload', handleBeforeunload)
                },
                {
                    deep: true,
                },
            )
        }, delay)
    }

    // 清除监听(在提交完成的跳转前主动调用)
    const resetFormPageLeavePrompt = () => {
        clearTimeout(watchTimer)
        watchStop?.()
        isFormEdit.value = false
        window.removeEventListener('beforeunload', handleBeforeunload)
    }

    onMounted(() => {
        watchFormData()
    })

    onUnmounted(() => {
        resetFormPageLeavePrompt()
    })

    return {
        isFormEdit,
        watchFormData,
        resetFormPageLeavePrompt,
    }
}

2.3 使用

import useFormPageLeavePrompt from '@/hooks/useFormPageLeavePrompt'

// 除非是特殊的场景一,否则不用传入 delay
const { isFormEdit, resetFormPageLeavePrompt } = useFormPageLeavePrompt([
    () => state.addForm,
    () => state.tableForm,
])

3 延迟加载组件

3.1 说明

作用:解决一次性循环渲染过多组件导致的页面白屏问题(一种性能优化手段)。

思路:利用 requestAnimationFrame(在每次重绘前执行)分批渲染。

3.2 实现

/*
 * 延迟加载组件
 */
 
import { ref, onMounted } from 'vue';

export default function useDefer(maxFrameCount) {
    const frameCount = ref(0);

    onMounted(() => {
        const refreshFrameCount = () => {
            requestAnimationFrame(() => {
                frameCount.value++;
                if (frameCount.value < maxFrameCount) {
                    refreshFrameCount();
                }
            });
        };
        refreshFrameCount();
    });

    function defer(showFrameCount) {
        return frameCount.value >= showFrameCount;
    }

    return {
        defer,
    };
}

3.3 使用

<div v-for="n in list">
    <template v-if="defer(n)">
        <HeavyComp />
    </template>
</div>

const { defer } = useDefer(list.length);

最后

欢迎沟通交流,提出改进意见,文章会更新补充。