利用Vue 的自定义指令实现,v-show, v-if

262 阅读4分钟

上一篇Vue 相关文章:

Vue3 watch 的六大特点

自定义指令基础回顾

生命周期

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

参数

指令的钩子会传递以下几种参数:

el:指令绑定到的元素。这可以用于直接操作 DOM。

binding:一个对象,包含以下属性。

value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。

oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。

arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。

modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。

instance:使用该指令的组件实例。 dir:指令的定义对象。 vnode:代表绑定元素的底层 VNode。

prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

指令的钩子会传递以下几种参数:

el:指令绑定到的元素。这可以用于直接操作 DOM。

binding:一个对象,包含以下属性。

value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。

oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。

arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。

modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。 instance:使用该指令的组件实例。 dir:指令的定义对象。 vnode:代表绑定元素的底层 VNode。

prevVnode:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

v-my-show 的实现

新建一个directive 文件夹

新建index.js : 所有指令的出口文件

新建myshow.js v-my-show的实现

myShow.js

function mounted (el, binding, vnode) {
  var isShow = binding.value
  el.style.display = isShow ? '' : 'none'
}

function updated (el, binding, vnode) {
  var isShow = binding.value
  el.style.display = isShow ? '' : 'none'
}

export default {
  mounted,
  updated
}

index.js

import myShow from './myshow'

export {
  myShow
}

使用:

<script setup>
import { ref } from 'vue'
import { myShow } from './directive'
const showHello = ref(false)
const vMyShow = myShow
</script>

<template>
  <div>
    <p v-my-show="showHello">你好, Vue</p>
    <button @click="showHello = !showHello">改变v-show {{ showHello }}</button>
  </div>
</template>

<style scoped>
</style>

效果:

image.png

可以看到,我们点击按钮, showHello 的值为true 的时候就显示了你好,Vue,为false 的时候就不显示,实现了和v-show 一样的功能。

代码优化: 从上面的代码我们看到,mounted 函数和updated 函数实现的功能逻辑是一样的, 在只使用了mounted 和updated生命周期函数的时候 可以直接导出函数,所以 myshow.js 可以改造成

export default function updated (el, binding, vnode) {
  var isShow = binding.value
  el.style.display = isShow ? '' : 'none'
}

再来看效果,还是一样的。

v-my-if实现

新建 myif.js

function mounted(el, binding) {
  var isShow = binding.value

  el.comNode = document.createComment('v-if')
  !isShow && el.parentNode.replaceChild(el.comNode, el)
}

function updated (el, binding) {
  var isShow = binding.value

  !isShow ? el.parentNode.replaceChild(el.comNode, el) : el.comNode.parentNode.replaceChild(el, el.comNode)
}

export default {
  mounted,
  updated
}

核心注意点:

(1)v-if 注释节点做占位符, Vue原生的v-if 也是用的v-if 注释节点,操作的时候可以打开控制台观察下。

(2) el 是一个对象, mounted的时候我们将注释节点保存在了这个对象上,方便再更新的时候使用。

index.js 导入

import myShow from './myshow'
import myIf from './myIf'

export {
  myShow,
  myIf
}

使用

<script setup>
import { ref } from 'vue'
import { myShow, myIf } from './directive'
const showHello = ref(false)
const vMyShow = myShow

const showHelloIf = ref(false)
const vMyIf = myIf
</script>

<template>
  <div>
    <p v-my-show="showHello">你好, Vue</p>
    <button @click="showHello = !showHello">改变v-show {{ showHello }}</button>

    <hr>
    <p v-my-if="showHelloIf">hello vMyIf</p>
    <button @click="showHelloIf = !showHelloIf">改变v-if {{ showHelloIf }}</button>
  </div>
</template>

<style scoped>
</style>

效果:

image.png

点击改变v-if,showHelloIf 为true 的时候, hello vMyIf 就显示出来了, showHelloIf 为false 的时候就hello vMyIf 就消失了。

问题: 现在我们再来点击下改变v-show的时发现控制台有报错

image.png

什么原因呢? 是因为,updated 在组件更新的时候凑会触发, 在点击改变v-show的时候,也触发了v-if里面的updated,所以报错了。

解决办法: 利用binding参数上的oldValue 和value 做对比, 如果两个值不一样,说明是当前指令的值变化导致的更新导致的,这种条件下才更新, 具体代码如下:

function updated (el, binding) {
  var isShow = binding.value
  var oldVal = binding.oldValue
  if (isShow !== oldVal) {
    !isShow ? el.parentNode.replaceChild(el.comNode, el) : el.comNode.parentNode.replaceChild(el, el.comNode)
  }
}

这时候我们再来操作下改变v-show ,就没有报错了

image.png

利用Vue 的指令实现v-show, v-if就分享到这里了,感谢收看