• 每日一题:vue3自定义指令大全(呕心沥血所作,附可运行项目源码)


    1.VUE常用指令大全

    本项目所有指令均为全局注册,使用时直接在组件中使用即可。

    指令目录:src/directives

    页面目录:src/views

    具体可查看源码

    1.1 权限指令

    封装一个权限指令,在模板中根据用户权限来控制元素的显示或隐藏。
    permission.js

    import { ref, watchEffect } from 'vue';
    
    const hasPermission = (permission) => {
        // 在实际项目中,根据后端返回的用户权限进行判断
        const userPermissions = ['view', 'edit'];
        return userPermissions.includes(permission);
    };
    
    export default {
        beforeMount(el, binding) {
            const { value } = binding;
            const visible = ref(false);
    
            // 监听权限变化,当权限发生改变时重新判断是否显示元素
            watchEffect(() => {
                visible.value = hasPermission(value);
            });
    
            // 根据 visible 的值来显示或隐藏元素
            el.style.display = visible.value ? 'block' : 'none';
        }
    }
    

    Permission.vue

    
    
    <script>
    export default {
      name: "Permission"
    }
    script>
    
    <style scoped>
    
    style>
    

    1.2 防抖函数指令

    在模板中使用防抖功能,可以用于减少频繁触发的事件的执行次数,比如在输入框中的实时搜索场景

    debounce.js

    //第一次触发也需要等待
    export default {
        beforeMount(el, binding) {
            const { value } = binding;
    
            // 需要回调函数以及监听事件类型
            if (typeof value.fn !== 'function' || !value.event) return;
    
            el.timer = null
            el.handler = function() {
                if (el.timer) {
                    clearTimeout(el.timer);
                    el.timer = null;
                };
                el.timer = setTimeout(() => {
                    binding.value.fn.apply(this, arguments)
                    el.timer = null;
                }, value.delay || 300);
            }
    
            el.addEventListener(value.event, el.handler)
        },
        beforeUnmount(el, binding) {
            // 在组件卸载前清除定时器,防止内存泄漏
            if (el.timer) {
                clearTimeout(el.timer);
                el.timer = null;
            }
            el.removeEventListener(binding.value.event, el.handler)
        }
    }
    

    Debounce.vue

    
    
    <script setup>
    import { ref } from 'vue';
    
    const inputValue = ref('');
    
    
    const handleInput = {
      event: 'input',
      fn (event) {
        console.log('Debounced Input:', event.target.value);
      },
      delay: 500
    }
    script>
    

    1.3 节流指令

    节流是限制执行频率,有节奏的执行,有规律, 更关注过程。一般用于 DOM 操作频次限制,优化性能,如拖拽、滚动、resize 等操作

    throttle.js

    // //第一次触发不直接执行回调函数
    // export default {
    //     mounted(el, binding) {
    //         // 至少需要回调函数以及监听事件类型
    //         if (typeof binding.value.fn !== 'function' || !binding.value.event) return;
    //         let delay = 200;
    //         el.timer = null;
    //         el.handler = function() {
    //             if (el.timer) return;
    //             el.timer = setTimeout(() => {
    //                 binding.value.fn.apply(this, arguments)
    //                 el.timer = null;
    //             }, binding.value.delay || delay);
    //         }
    //         el.addEventListener(binding.value.event, el.handler)
    //     },
    //     // 元素卸载前也记得清理定时器并且移除监听事件
    //     beforeUnmount(el, binding) {
    //         if (el.timer) {
    //             clearTimeout(el.timer);
    //             el.timer = null;
    //         }
    //         el.removeEventListener(binding.value.event, el.handler)
    //     }
    // }
    // //第一次触发直接执行回调函数
    export default {
        mounted(el, binding) {
            // 至少需要回调函数以及监听事件类型
            if (typeof binding.value.fn !== 'function' || !binding.value.event) return;
    
            let isFirstTrigger = true; // 判断是否是第一次触发
            let delay = 200;
    
            el.timer = null;
            el.handler = function () {
                if (el.timer) return;
    
                if (isFirstTrigger) {
                    binding.value.fn.apply(this, arguments); // 第一次触发直接执行回调函数
                    isFirstTrigger = false;
                    return;
                }
    
                el.timer = setTimeout(() => {
                    binding.value.fn.apply(this, arguments);
                    el.timer = null;
                }, binding.value.delay || delay);
            };
    
            el.addEventListener(binding.value.event, el.handler);
        },
        // 元素卸载前也记得清理定时器并且移除监听事件
        beforeUnmount(el, binding) {
            if (el.timer) {
                clearTimeout(el.timer);
                el.timer = null;
            }
            el.removeEventListener(binding.value.event, el.handler);
        },
    };
    

    Throttle.vue

    
    
    <script setup>
      const handleTest = {
        event: 'click',
        fn (event) {
          console.log('Throttled Click:', event.target);
        },
        delay: 1000
      }
    script>
    
    <style scoped>
    
    style>
    

    1.4 resize 指令

    resize 在模板中使用该指令来监听元素大小的变化,执行相应的业务逻辑代码
    resize.js

    import { ref, onMounted, onUnmounted } from 'vue';
    
    export default {
        mounted(el, binding) {
            const { value: callback } = binding;
            const width = ref(0);
            const height = ref(0);
            console.log('callback',callback)
            function handleResize() {
                width.value = el.clientWidth;
                height.value = el.clientHeight;
                callback({ width: width.value, height: height.value });
            }
            // 监听窗口大小变化,调用 handleResize
            window.addEventListener('resize', handleResize);
    
            // 初始时调用一次 handleResize
            handleResize();
    
            // 在组件卸载前移除事件监听
            onUnmounted(() => {
                window.removeEventListener('resize', handleResize);
            });
        }
    }
    
    
    <script setup>
    import { ref } from 'vue';
    const windowWidth = ref(window.innerWidth);
    const windowHeight = ref(window.innerHeight);
    const resize=(size)=>{
      console.log('Window size changed:', size);
    }
    
    script>
    

    1.5 滚动加载指令

    封装一个滚动加载监听指令,在模板中使用该指令来实现滚动加载的功能

    scrollLoad.js

    import { onMounted, onUnmounted } from 'vue';
    
    export default {
        mounted(el, binding) {
            const { fn, distance = 100 } = binding.value;
            console.log(fn)
            console.log(el)
            console.log(distance)
            function handleScroll() {
                const scrollHeight = el.scrollHeight;
                const offsetHeight = el.offsetHeight;
                const scrollTop = el.scrollTop;
                if (scrollHeight - offsetHeight - scrollTop <= distance) {
                    fn();
                }
            }
    
            // 监听滚动事件,调用 handleScroll
            el.addEventListener('scroll', handleScroll);
    
            // 在组件卸载前移除事件监听
            onUnmounted(() => {
                el.removeEventListener('scroll', handleScroll);
            });
        }
    }
    

    scrollLoad.vue

    
    
    <script setup>
    import { ref } from 'vue';
    
    const items = ref([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15])
    
    
    const loadMore = {
      fn() {
        // 模拟加载更多数据
        for (let i = 0; i < 10; i++) {
          items.value.push('Item ' + (items.value.length + 1));
        }
      },
      distance: 100
    }
    script>
    

    1.6 图片懒加载指令

    在图片元素上使用指令,实现图片的懒加载
    lazyLoad.js

    export default {
        mounted(el, binding) {
            // 使用 Intersection Observer 实现图片懒加载
            const io = new IntersectionObserver((entries, observer) => {
                entries.forEach(entry => {
                    if (entry.isIntersecting) {
                        el.src = binding.value;
                        observer.unobserve(el);
                    }
                });
            });
            io.observe(el);
        }
    }
    

    lazyLoad.vue