Vue2之指令

常见指令

vue中的指令,vue中都是以v-开头 (一般用来操作dom)

  • v-once 渲染一次 (可用作优化,但是使用频率极少)
  • v-html 将字符串转化成dom插入到标签中 (会导致xss攻击问题,并且覆盖子元素)
  • v-if/v-else/v-else-if 不满足时dom不存在(可以使用template标签)
  • v-show 不满足时dom隐藏 (不能使用template标签)
  • v-for 循环字符串、对象、数字、数组 (循环时必须加key,尽量不采用索引)
  • v-bind 可以简写成: 属性(style、class…)绑定
  • v-on 可以简写成@ 给元素绑定事件 (常用修饰符 .stop、.prevent、.self、.once、.passive)
  • v-model双向绑定 (支持.trim、.number修饰符)

自定义指令

指令钩子

指令钩子函数

一个指令定义对象可以提供如下几个钩子函数 (均为可选):

  • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。

  • inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。

  • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新。

  • componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。

  • unbind:只调用一次,指令与元素解绑时调用。

Vue.directive('gqs',{
    bind() {
      // 当指令绑定到 HTML 元素上时触发.**只调用一次**
      console.log('bind triggerd')
    },
    inserted() {
      // 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 `div#app`)**.但不保证,父元素已经插入了 DOM 文档.**
      console.log('inserted triggerd')
    },
    updated() {
      // 所在组件的`VNode`更新时调用.
      console.log('updated triggerd')
    },
    componentUpdated() {
      // 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
      console.log('componentUpdated triggerd')
      
    },
    unbind() {
      // 只调用一次,指令与元素解绑时调用.
      console.log('unbind triggerd')
    }
  })

指令钩子函数参数

  • 每个钩子函数有以下参数:

    • el:指令所绑定的元素,可以用来直接操作 DOM。
    • binding:一个对象,包含以下 property:
    • name:指令名,不包括 v- 前缀。
    • value:指令的绑定值,例如:v-my-directive=“1 + 1” 中,绑定值为 2。
    • oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
    • expression:字符串形式的指令表达式。例如 v-my-directive=“1 + 1” 中,表达式为 “1 + 1”。
    • arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 “foo”。
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
    • vnode:Vue 编译生成的虚拟节点。
    • oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
  • 注意:bind和insert都只调用一次;bind在insert前执行,也就是bind在dom树绘制前调用,insert在dom树绘制后调用;涉及dom操作的,我们一般都用insert,如自动获取焦点指令,只能用insert函数。

v-click-outside

推荐插件:https://www.npmjs.com/package/v-click-outside

点击区域以外关闭

src                          
├─ App.vue                         
└─ main.js    

main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false
Vue.directive('clickOutside',{
  bind(el,bindings,vnode){
      el.handler = function (e) {
          if(!el.contains(e.target)){
              let method = bindings.expression;
              // 触发指定绑定的方法
              vnode.context[method]();
          }
      } 
      document.addEventListener('click',el.handler)
  },
  unbind(el){ 
      document.removeEventListener('click',el.handler)
  }
})

new Vue({
  render: h => h(App),
}).$mount('#app')

App.vue

<template>
  <div>
    <div v-click-outside="blur">
      <input type="text" @focus="focus">
      <div v-show="visible">显示面板</div>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      visible: false,
    };
  },
  created() {},
  mounted() {},
  methods: {
    focus() {
      this.visible = true;
    },
    blur() {
      this.visible = false;
    },
  },
};
</script>

<style scoped lang="less">
</style>

v-lazy

  • 官方推荐:https://www.npmjs.com/package/vue-lazyload

vue-lazyload.js

// 获取滚动区域的父元素
const getScrollParent = (el) => {
  let parent = el.parentNode;
  while (parent) {
    if (/scroll/.test(getComputedStyle(parent)['overflow'])) {
      return parent;
    }
    parent = parent.parentNode;
  }
  return parent;
}
const loadImageAsync = (src, resolve, reject) => {
  let image = new Image();
  image.src = src;
  image.onload = resolve;
  image.onerror = reject
}
const Lazy = (Vue) => {
  // 每一个图片元素,都构造成一个类的实例
  class ReactiveListener {
    constructor({ el, src, elRenderer, options }) {
      this.el = el;
      this.src = src;
      this.elRenderer = elRenderer;
      this.options = options;
      // 定义状态
      this.state = { loading: false }
    }
    // 检测图片是否在可视区内
    checkInView() {
      let { top } = this.el.getBoundingClientRect();
      return top < window.innerHeight * this.options.preLoad
    }
    load() {
      // 先加载loading
      this.elRenderer(this, 'loading');
      // 如果加载成功,显示正常图片
      loadImageAsync(this.src, () => {
        this.state.loading = true; // 加载完毕了
        this.elRenderer(this, 'loadinged');
      }, () => {
        this.elRenderer(this, 'error');
      });
    }
  }
  return class LazyClass {
    constructor(options) {
      // 保存用户传入的属性
      this.options = options;
      // 记录是否绑定过处理函数
      this.bindHandler = false
      // 存储元素实例
      this.listenerQueue = []
    }
    handleLazyLoad() {
      // 检查是否应该显示图片 计算当前图片是否在可视区
      this.listenerQueue.forEach(listener => {
        // 默认不在区域
        if (!listener.state.loading) {
          // 没有加载则加载
          let catIn = listener.checkInView()
          catIn && listener.load()
        }
      })
    }
    add(el, bindings, vnode) {
      // 找到父亲元素(包含overflow属性的元素)
      Vue.nextTick(() => {
        // 带有滚动的盒子
        let scrollParent = getScrollParent(el)
        // 获取链接
        let src = bindings.value;
        if (!this.bindHandler) {
          // 绑定事件
          this.bindHandler = true
          scrollParent.addEventListener('scroll', this.handleLazyLoad.bind(this))
        }
        // 判断当前元素是否容器可视区内,如果不是则不用渲染
        const listener = new ReactiveListener({
          el,
          src,
          options: this.options,
          elRenderer: this.elRenderer.bind(this)
        })
        this.listenerQueue.push(listener)
        // 判断数组里图片,哪些需要显示,哪些不需要显示
        this.handleLazyLoad()
      })
    }
    // 渲染图片
    elRenderer(listener, state) {
      let el = listener.el;
      let src = '';
      switch (state) {
        case 'loading':
          src = listener.options.loading || ''
          break;
        case 'error':
          src = listener.options.error || ''
        default:
          src = listener.src;
          break;
      }
      el.setAttribute('src', src)
    }
  }
}
const VueLazyload = {
  install(Vue, options) {
    const LazyClass = Lazy(Vue);
    const lazy = new LazyClass(options);
    Vue.directive('lazy', {
      bind: lazy.add.bind(lazy)
    });
  }
}

index.html


<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.8/vue.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.7.2/axios.js"></script>
  <script src="./vue-lazyload.js"></script>


  <div id="app">
    <div class="box">
      <li v-for="img in imgs" :key="img">
        <img v-lazy="img" >
      </li>
    </div>
  </div>

  <script>
    const loading = 'http://localhost:3000/images/0.gif';
    Vue.use(VueLazyload, {
      preLoad: 0.3, // 可见区域的1.3倍
      loading, // loading图
    })
    const vm = new Vue({
      el: '#app',
      data() {
        return {
          imgs: []
        }
      },
      created() {
        axios.get('http://localhost:3000/api/img').then(({ data }) => {
          
          this.imgs = data;
        })
      }
    });
  </script>
  <style>
    .box {
      height: 300px;
      overflow: scroll;
      width: 200px;
    }

    img {
      width: 100px;
      height: 100px;
    }
  </style>

</body>

</html>
  • 40
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值