利用Vue自定义指令 - 让你的开发变得更简单

简介: 学习Vue自定义指令及案例分享

前段时间在用框架开发H5页面时,碰到框架中的组件内置了一个属性用于适配异形屏,虽然是组件内部实现的,但这个方式让我萌生一个想法:能不能自己写一个属性来实现这样的功能?

image.png

经过一番思索,我发现Vue的指令模式就很像属性的写法,在Vue中,我们利用模板指令诸如v-if v-for等完成了许多工作,而Vue同样也支持自定义属性:

const app = Vue.createApp({})
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
  // 当被绑定的元素挂载到 DOM 中时……
  mounted(el) {
    // 聚焦元素
    el.focus()
  }
})

然后你可以在模板中任何元素上使用新的 v-focus attribute,如下

<input v-focus />

注:这里除了全局注册,也可以采用局部注册的方式,实际开发中可以使用vue另一项方便的功能mixin来将对应的指令混入你想使用的文件中,以达到代码的复用,那么开始进入正题吧。

底部安全区适配

首先页面必须在 head 标签中添加 meta 标签,并设置 viewport-fit=cover 值
directives: {
    safeAreaBottom: {
      bind(el, binding) {
        const addHigh = binding.value || 0
        el.setAttribute('style', el.style.cssText + `padding-bottom: calc(${addHigh} +  constant(safe-area-inset-bottom));padding-bottom: calc(${addHigh} +  env(safe-area-inset-bottom));`);
      }
    }
}

使用:

<div v-safe-area-bottom></div>

如果设计图本身存在一个边距,则可以动态适配:

<div v-safe-area-bottom="'1rem'"></div>
<div v-safe-area-bottom="'10px'"></div>

是不是很方便?我们再来看看另一个移动端H5会遇到的问题,并且还是用Vue指令来解决它。

弹窗背景页不滚动

在移动端开发中,页面弹出滚动窗口时,需要将背景页固定住不动,否则会出现"滚动穿透"的现象。

touchScroll: {
  inserted() {
    const scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
    document.body.style.cssText += 'position:fixed;width:100%;top:-' + scrollTop + 'px;';
  },
  unbind() {
    const body = document.body || document.documentElement;
    body.style.position = '';
    const top = body.style.top;
    document.body.scrollTop = document.documentElement.scrollTop = -parseInt(top, 10);
    body.style.top = '';
  }
}
<div v-touch-scroll>是的,我是一个弹窗,当我出现时我的背景会吓得不敢动</div>

实现一个copy工具

有时我们需要页面点击可以"一键复制"的功能,可能大家都有用到一个叫vue-clipboard的库,知道了指令的使用,实现一个copy自然也不在话下,那么就自己动手写一个vueCopy,为今后开发项目减少一个第三方库的使用吧。

首先我们看看这个工具是怎么使用的:

image.png

可以看出作者也是利用了指令,就照他这个思路,动手撸了一个,这里就直接上代码了,具体思路点见注释:

clipboard: {
  bind(el, binding, { context }) {
    const _this = context
    // 利用arg用来注入回调函数
    if (binding.arg === 'success') {
      _this.__clipboardSuccess = _this[binding.expression]
    } else if (binding.arg === 'error') {
      _this.__clipboardError = _this[binding.expression]
    } else { // 正常情况下就将文字缓存起来
      _this.__clipboardValue = binding.value
    }
    el.handler = () => {
      if (!_this.__clipboardValue) {
        this.__clipboardError && this.__clipboardError('无内容')
        return
      }
      if (binding.arg) { // 这里是因为属性被我们用了多次会多次执行,所以限制了执行次数
        return
      }
      try {
        const textarea = document.createElement('textarea')
        textarea.readOnly = 'readonly' // 禁止输入, readonly 防止手机端错误聚焦自动唤起键盘
        textarea.setAttribute('style', 'position:fixed;top:-9999px;left:-9999px;') // 它是可见的,但它又是不可见的
        textarea.value = binding.value
        document.body.appendChild(textarea)
        textarea.select()
        const result = document.execCommand('Copy')
        if (result) {
          _this.__clipboardSuccess && _this.__clipboardSuccess(binding.value) // 这里可以定义成功回调返回的数据
        }
        document.body.removeChild(textarea)
      } catch (e) {
        this.__clipboardError && this.__clipboardError(e)
      }
    }
    el.addEventListener('click', el.handler)
  },
  componentUpdated(el, { arg, value }, { context }) { // 更新值时候触发
    const _this = context
    if (!arg) { // 注册回调的部分不要赋值
      _this.__clipboardValue = value
    }
  },
  unbind(el) {
    el.removeEventListener('click', el.handler)
  },
}

简单使用:

<div v-clipboard="'copy copy Text'">点击直接复制到剪贴板</div>

带回调的使用:

<template>
    <div v-clipboard="text" v-clipboard:success="success" v-clipboard:error="error">copy copy Text</div>
</template>

<script>
export default {
  data() {
    return {
      text: 123
    }
  },
  methods: {
    success(e) {
      console.log(e); // 复制成功回调
    },
    error(e) {
      console.log(e); // 复制失败回调
    }
  }
}
</script>

表单防止重复提交

// 设置 v-throttle 自定义指令
Vue.directive('throttle', {
  bind: (el, binding) => {
    let throttleTime = binding.value; // 节流时间
    if (!throttleTime) { // 用户若不设置节流时间,则默认2s
      throttleTime = 2000;
    }
    let cbFun;
    el.addEventListener('click', event => {
      if (!cbFun) { // 第一次执行
        cbFun = setTimeout(() => {
          cbFun = null;
        }, throttleTime);
      } else {
        event && event.stopImmediatePropagation();
      }
    }, true);
  },
});

使用:

<button @click="sayHello" v-throttle>提交</button>

图片懒加载

const LazyLoad = {
    // install方法
    install(Vue,options){
          // 代替图片的loading图
        let defaultSrc = options.default;
        Vue.directive('lazy',{
            bind(el,binding){
                LazyLoad.init(el,binding.value,defaultSrc);
            },
            inserted(el){
                // 兼容处理
                if('IntersectionObserver' in window){
                    LazyLoad.observe(el);
                }else{
                    LazyLoad.listenerScroll(el);
                }
                
            },
        })
    },
    // 初始化
    init(el,val,def){
        // data-src 储存真实src
        el.setAttribute('data-src',val);
        // 设置src为loading图
        el.setAttribute('src',def);
    },
    // 利用IntersectionObserver监听el
    observe(el){
        let io = new IntersectionObserver(entries => {
            let realSrc = el.dataset.src;
            if(entries[0].isIntersecting){
                if(realSrc){
                    el.src = realSrc;
                    el.removeAttribute('data-src');
                }
            }
        });
        io.observe(el);
    },
    // 监听scroll事件
    listenerScroll(el){
        let handler = LazyLoad.throttle(LazyLoad.load,300);
        LazyLoad.load(el);
        window.addEventListener('scroll',() => {
            handler(el);
        });
    },
    // 加载真实图片
    load(el){
        let windowHeight = document.documentElement.clientHeight
        let elTop = el.getBoundingClientRect().top;
        let elBtm = el.getBoundingClientRect().bottom;
        let realSrc = el.dataset.src;
        if(elTop - windowHeight<0&&elBtm > 0){
            if(realSrc){
                el.src = realSrc;
                el.removeAttribute('data-src');
            }
        }
    },
    // 节流
    throttle(fn,delay){
        let timer; 
        let prevTime;
        return function(...args){
            let currTime = Date.now();
            let context = this;
            if(!prevTime) prevTime = currTime;
            clearTimeout(timer);
            
            if(currTime - prevTime > delay){
                prevTime = currTime;
                fn.apply(context,args);
                clearTimeout(timer);
                return;
            }

            timer = setTimeout(function(){
                prevTime = Date.now();
                timer = null;
                fn.apply(context,args);
            },delay);
        }
    }

}
export default LazyLoad;
相关文章
|
7天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
8天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
8天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
8天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
7天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
9天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
7天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
21天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
9天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
14天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发