vue指令封装(按钮权限、loading加载、slideIn窗口进入动画)

发布时间 2023-11-27 17:16:25作者: Cxymds

vue 指令

vue 本身具有一些指令,但是有些指令是 vue 作者自己写的,有些是第三方插件写的。

v-if

v-if 指令是用来控制元素是否显示的,如果值为 true,则显示,如果值为 false,则隐藏。

<div id="app">
  <p v-if="isShow">我是显示的内容</p>
  <p v-else>我是隐藏的内容</p>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      isShow: true,
    },
  });
</script>

v-for

v-for 指令是用来遍历数组或者对象,遍历的结果会被渲染到页面上。

<div id="app">
  <ul>
    <li v-for="item in list">{{item.name}}</li>
  </ul>
</div>
<script>
  var vm = new Vue();
  vm.list = [{ name: "张三" }, { name: "李四" }, { name: "王五" }];
  vm.$mount("#app");
  // vm.$mount('#app')  // 也可以写成 vm.$mount('#app'),因为vm.$mount('#app')内部也是调用了vm.$mount('#app'),所以可以写成vm.$mount('#app'),因为vm.$mount('#app')内部也是调用了vm.$mount('#app'),所以可以写成vm.$mount('#app')
</script>

v-show

v-show 指令是用来控制元素是否显示的,如果值为 true,则显示,如果值为 false,则隐藏。

<div id="app">
  <p v-show="isShow">我是显示的内容</p>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      isShow: true,
    },
  });
  // 等价于
  var vm = new Vue({
    el: "#app",
    data: {
      isShow: true,
    },
    directives: {
      show: {
        update: function (value) {
          this.el.style.display = value ? "" : "none";
        },
      },
    },
  });
</script>

v-text

v-text 指令是用来显示文本的,如果值为字符串,则显示,如果值为变量,则显示变量的值。

<div id="app">
  <p v-text="msg"></p>
</div>
<script>
  var vm = new Vue(
      {
          el: '#app',
      }
      msg: '我是文本'
  )
</script>

v-html

v-html 指令是用来显示 html 的,如果值为字符串,则显示,如果值为变量,则显示变量的值。

<div id="app">
  <p v-html="msg"></p>
</div>
<script>
  var vm = new Vue(
      {
          el: '#app',
      }
      msg: '<span style="color: red">我是文本</span>'
  )
</script>

v-bind

v-bind 指令是用来绑定元素属性的,如果值为字符串,则绑定元素的属性,如果值为变量,则绑定变量的值。

<div id="app">
  <input type="text" v-bind:value="msg" />
</div>
<script>
  var vm = new Vue(
      {
          el: '#app',
      }
      msg: '我是文本'
  )
  // 等价于
  var vm = new Vue(
      {
          el: '#app',
      }
      msg: '我是文本'
  )
</script>

v-on

v-on 指令是用来绑定元素事件的,如果值为字符串,则绑定元素的事件,如果值为变量,则绑定变量的值。

<div id="app">
  <button v-on:click="showMsg">点我</button>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    methods: {
      showMsg: function () {
        alert("我是按钮");
      },
    },
  });
</script>

v-bind:class

v-bind:class 指令是用来绑定元素的 class 属性的,如果值为字符串,则绑定元素的 class 属性,如果值为变量,则绑定变量的值。

<div id="app">
  <div v-bind:class="isShow?'show' : 'hide'">
    我是显示的内容
    <span v-bind:class="isShow?'show' : 'hide'">我是隐藏的内容</span>
  </div>
</div>
<script>
  var vm = new Vue({
    el: "#app",
    data: {
      isShow: true,
    },
  });
</script>

v-model

v-model 指令是用来双向绑定表单元素的,如果值为字符串,则绑定元素的 value 属性,如果值为变量,则绑定变量的值。

<div id="app">
  <input type="text" v-model="msg" />
</div>
<script>
  var vm = new Vue();
  vm.msg = "我是文本";
  vm.$mount("#app");
</script>

v-pre

v-pre 指令是用来跳过这个元素和它的子元素的编译过程。

<div id="app">
  <p v-pre>我是文本</p>
</div>
<script>
  var vm = new Vue();
  vm.$mount("#app");
</script>

v-cloak

v-cloak 指令是用来隐藏元素直到编译完成。

<div id="app" v-cloak>
  <p>我是文本</p>
</div>
<script>
  var vm = new Vue();
  vm.$mount("#app");
</script>

v-once

v-once 指令是用来只渲染元素和它的子元素一次。

<div id="app">
  <p v-once>我是文本</p>
</div>
<script>
  var vm = new Vue();
  vm.$mount("#app");
</script>

v-pre

v-pre 指令是用来跳过这个元素和它的子元素的编译过程。

<div id="app">
  <p v-pre>我是文本</p>
</div>
<script>
  var vm = new Vue();
  vm.$mount("#app");
</script>

v-cloak

v-cloak 指令是用来隐藏元素直到编译完成。

<div id="app" v-cloak>
  <p>我是文本</p>
</div>
<script>
  var vm = new Vue();
  vm.$mount("#app");
</script>

打住,本来就是准备一个简单的介绍,不准备详细介绍这些指令,但是奈何 GPT 功能强大。@@!

自定义指令

自定义指令是一种代码重游的方式,可以将一些行为抽取出来,封装成一个指令,在元素上使用这个指令,就可以让页面上相应的行为发生变化。自定义指令主要是为了重用涉及普通元素的底层 DOM 访问的逻辑。
以下是一个简单的官方案例

选项式格式:

<template>
  <input v-focus />
</template>

<script setup>
  const focus = {
    mounted: (el) => el.focus(),
  };

  export default {
    directives: {
      // 在模板中启用 v-focus
      focus,
    },
  };
</script>

组合式格式:

<script setup>
  // 在模板中启用 v-focus
  const vFocus = {
    mounted: (el) => el.focus(),
  };
</script>

<template>
  <input v-focus />
</template>

<script setup> 中,任何以 v 开头的驼峰式命名的变量都可以被用作一个自定义指令。在上面的例子中,vFocus 即可以在模板中以 v-focus 的形式使用。在没有使用 <script setup> 的情况下,自定义指令需要通过 directives 选项注册

指令钩子

一个自定义指令由一个包含类似组件生命周期钩子的对象来定义。钩子函数会接收到指令所绑定元素作为其参数。

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {},
};

这里需要注意的式指令的钩子函数,在调用时,会将当前元素、指令对象、虚拟节点、上一个虚拟节点作为参数传递给钩子函数。

指令参数

指令的参数可以是以下几种类型:

  • 字符串:如果指令的值是一个字符串,则会被当作 CSS 类名来绑定到元素上。
  • 对象:如果指令的值是一个对象,则会基于对象的值来设置元素的属性。
  • 表达式:如果指令的值是一个表达式,则会基于表达式的值来设置元素的属性。
  • 函数:如果指令的值是一个函数,则会调用该函数,并将函数的返回值作为指令的绑定值。

指令的绑定值

指令的绑定值是由指令的定义函数接收到的参数,在指令的钩子函数中,可以通过 binding.value 访问到。

const myDirective = {
  //...
  // 指令的绑定值
  bind(el, binding, vnode, prevVnode) {
    // 绑定值
    console.log(binding.value);
  },
};

指令的更新值

指令的更新值是由指令的更新函数接收到的参数,在指令的钩子函数中,可以通过 binding.value 访问到。

const myDirective = {
  //...
  // 指令的更新值
  update(el, binding, vnode, prevVnode) {
    // 更新值
    console.log(binding.value);
  },
};

指令的 oldValue

指令的 oldValue 是一个只读的属性,它的值就是指令的绑定值在上一次更新时的值。仅在beforeUpdateupdated 中可用。无论值是否更改,它都可用。

const myDirective = {
  //...
  // 指令的 oldValue
  update(el, binding, vnode, prevVnode) {
    // 更新值
    console.log(binding.value);
    if (binding.value) {
      // 指令的 oldValue
      console.log(binding.oldValue);
    }
  },
};

指令的 arg

指令的 arg 是一个只读的属性,它的值就是指令名之后的第一个冒号后面的值。
例如在 v-my-directive:foo 中,参数是 "foo"。

指令的 modifiers

指令的 modifiers 是一个只读的对象,它包含了指令名之后的所有的冒号后面的修饰符。
例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。

const myDirective = {
  // 指令的 modifiers
  update(el, binding, vnode, prevVnode) {
    // modifiers
    console.log(binding.modifiers);
  },
};

指令的 vnode

指令的 vnode 是一个只读的对象,它包含了当前指令所绑定的虚拟节点。

const myDirective = {
  // 指令的 vnode
  update(el, binding, vnode, prevVnode) {
    // vnode
    console.log(binding.vnode);
  },
};

指令的 prevNode

指令的 prevNode 是一个只读的对象,它包含了上一个虚拟节点。代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

const myDirective = {
  // 指令的 prevNode
  update(el, binding, vnode, prevVnode) {
    // prevNode
    console.log(binding.prevNode);
  },
};

举例来说,像下面这样使用指令:

<div v-example:foo.bar="baz"></div>

binding 参数会是一个这样的对象:

{
  arg: 'foo',
  modifiers: { bar: true },
  value: /* `baz` 的值 */,
  oldValue: /* 上一次更新时 `baz` 的值 */
}

除了 el 外,其他参数都是只读的,不要更改它们。若你需要在不同的钩子间共享信息,推荐通过元素的 dataset attribute 实现。
在指令钩子中,可以通过 el.dataset 访问到元素的 dataset attribute。

简化指令

对于自定义指令来说,一个很常见的情况是仅仅需要在 mounted 和 updated 上实现相同的行为,这样可以使得指令更加简洁。

app.directive("color", (el, binding) => {
  // 这会在 `mounted` 和 `updated` 时都调用
  el.style.color = binding.value;
});

在组件上使用指令

当在组件上使用自定义指令时,它会始终应用于组件的根节点,和透传 attributes 类似。

<div id="app">
  <my-component v-color="red"></my-component>
</div>
<!-- MyComponent 的模板 -->

<div>
  <!-- 指令会被应用在此处 -->
  <span>My component content</span>
</div>

vue 插件语法

一般我们会将指令做成 vue 插件,这样可以方便我们复用,并且可以避免一些全局变量污染。
vue 暴露出来一个扩展方法,install 方法里面可以直接添加插件。

export default {
  install(app) {
    //...
  },
};

封装按钮权限指令

app.directive("permisson", {
  mounted(el, binding) {
    if (!binding.value) return false;
    const userStore = useUserStore();
    if (!userStore.buttons.includes(binding.value)) {
      el.parentNode.removeChild(el);
    }
  },
});

封装加载动画指令

app.directive("preload", (el, binding) => {
  el.style.position = "relative";
  const vnode = h(FxLoading, {
    isShow: binding.value,
    sec: binding.arg && parseInt(binding.arg),
  });
  render(vnode, el);
});

封装窗口元素进入动画

//动画距离
const DISTANCE = 100;  、
//动画时间
const DURATION = 500;

// 动画对应关系映射
const animationMap = new WeakMap();
// 窗口监视器
const ob = new IntersectionObserver((entires) => {
  for (const entry of entires) {
    if (entry.isIntersecting) {
      animationMap.get(entry.target).play();
      ob.unobserve(entry.target);
    }
  }
});

/**
 * 判断给定的元素是否在视图范围之外
 * @param {HTMLElement} el - 要判断的元素
 * @returns {boolean} - 如果元素在视图范围之外则返回true,否则返回false
 */
function isBelowViewPort(el) {
  const rect = el.getBoundingClientRect();
  return rect.top > window.innerHeight;
}
app.directive("slideIn", {
  mounted(el) {
    setTimeout(() => {
      if (!isBelowViewPort(el)) {
        // 只有当元素在视口top值下面的时候才会触发animate
        return;
      }
      const animation = el.animate(
        [
          {
            transform: `translateY(${DISTANCE}px)`,
            opacity: 0.5,
          },
          {
            transform: `translateY(0px)`,
            opacity: 1,
          },
        ],
        {
          duration: DURATION,
          easing: "ease",
        }
      );
      animation.pause();
      animationMap.set(el, animation);
      ob.observe(el);
    }, 0);
  },
  unmounted(el) {
    ob.unobserve(el);
  },
});