从0到Vue3企业项目实战【07.自定义组件进阶与自定义指令】

301 阅读5分钟

这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

1.0 动态组件

当我们要创建两个组件,使他们互斥显示以及隐藏时,我们第一时间会想到使用v-if等vue指令实现,那么我们可不可以不用v-if实现这种效果呢,答案是可以的,使用动态组件就行,动态组件是用于实现多个组件在同一个挂载点,动态进行切换的

假如我们要实现一个类似于掘金我的主页内的Tab栏切换的一个功能

动画.gif

如果使用v-show来实现,我们需要为每一个Tab栏选项定义一个变量,当选中的Tab栏发生改变的时候,变量也跟随之改变即定义一个current值,当为动态则为0,当为文章即为1依次类推,根据选中的Tab栏来确定我们当前的current值,而下方的组件们,则通过v-show="current == 0"来控制显影,这样就可以实现多个组件的显影控制了

在大部分的情况下我们都是如此解决此类问题的,但是在vue中有更好的解决方案 -- 动态组件,使用动态组件我们无需为每一个组件书写不同的显影规则,依靠<component>中的:is属性即可实现组件间的动态切换,我们动手写一下简单的案例感受一下两种解决方案的优劣

1.1 动态组件实践

我们优先制作一个表头Tab栏组件,取名为listHeader.vue

image.png

代码结构和逻辑如下

listHeader.vue

<template>
  <div class="header">
    <span
      :class="{active: current == index,item}"
      v-for="(item,index) in list"
      :key="index"
      @click="changeActive(index)"
    >{{item}}</span>
  </div>
</template>

<script>
export default {
  props: ['list', 'current'],
  methods: {
    changeActive (index) {
      this.$emit('change', index)
    }
  }
}
</script>

<style scoped>
.header {
  height: 49px;
  width: 708px;
  background-color: #fff;
  margin: 20px auto 0 auto;
  display: flex;
  border-radius: 0.2rem 0.2rem 0 0;
  border-bottom: 1px solid #e4e6eb;
  color: #515767;
  cursor: pointer;
}

.item:hover {
  color: #747985;
}

.item {
  font-size: 16px;
  height: 49px;
  width: 72px;
  line-height: 49px;
  text-align: center;
  color: #252933;
  font-weight: 500;
  position: relative;
}

.active::after {
  content: '';
  position: absolute;
  width: 16px;
  height: 3px;
  border-radius: 50px;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  z-index: 10;
  background-color: #1e80ff;
}
</style>

父组件传递一个list用于Tab栏循环,采用了index作为当前选中的current值,当点击某一项则会调用父组件的方法change并传递给index供父组件完成修改

这里同样用App.vue作为演示的主页面

App.vue

<template>
  <div>
    <list-header :list="list" :current="current" @change="changeCurrent"></list-header>
  </div>
</template>

<script>
import listHeader from './components/listHeader.vue';

export default {
  components: { listHeader },
  data () {
    return {
      current: 0,
      list: ['动态', '文章', '专栏', '沸点', '收藏集']
    };
  },
  methods: {
    changeCurrent (index) {
      this.current = index
    }
  }
}
</script>

<style>
body {
  background-color: #f4f5f5;
}
</style>

此时Tab栏组件算是大功告成了算是比较简化的一个版本,作为本次的Demo没有问题,效果如图

动画.gif

这里再做几个简化的小组件作为切换的组件,这里就不一一画了,采用文字来确定是哪个组件

创建以下组件,代码不同点就是文字不同,其他都一样,并且引入到app.vue

image.png

<template>
  <div class="msg">动态</div>
</template>

<script>
export default {

}
</script>
<style scoped>
.msg {
  background-color: #fff;
  width: 708px;
  height: 221px;
  margin: 0 auto;
}
</style>

当我们采取v-show的方式来控制显影,那么在app.vue中我们需要如下操作

  • 引入组件并注册组件
  • 为每一个组件写上显影条件
<template>
  <div>
    <list-header :list="list" :current="current" @change="changeCurrent"></list-header>
    <DT v-show="current == 0"></DT>
    <WZ v-show="current == 1"></WZ>
    <ZL v-show="current == 2"></ZL>
    <FD v-show="current == 3"></FD>
    <SCJ v-show="current == 4"></SCJ>
  </div>
</template>

<script>
import DT from './components/jueJinDemo/DT.vue';
import FD from './components/jueJinDemo/FD.vue';
import SCJ from './components/jueJinDemo/SCJ.vue';
import WZ from './components/jueJinDemo/WZ.vue';
import ZL from './components/jueJinDemo/ZL.vue';
import listHeader from './components/listHeader.vue';

export default {
  components: { listHeader, DT, FD, SCJ, WZ, ZL },
  data () {
    return {
      current: 0,
      list: ['动态', '文章', '专栏', '沸点', '收藏集']
    };
  },
  methods: {
    changeCurrent (index) {
      this.current = index
    }
  }
}
</script>

<style>
body {
  background-color: #f4f5f5;
}
</style>

此时我们已经可以通过tab栏组件完成内容的切换了

动画.gif

这里仅仅有五个组件需要进行切换,那如果是十个是一百个我们也要这样一个个的写切换条件吗,不够优雅,我们采用动态组件的方式可以杜绝这种现象

动态组件类似于动态属性,是通过绑定一个变量来控制该挂载点显示什么组件,主要的使用步骤如下

  1. 准备变量来储存要显示的组件名,以及组件名对应的list
  2. 设置挂载点<component>,使用is属性来设置要显示什么组件
  3. 点击Tab栏修改变量内的组件名

App.vue改为如下代码即可

<template>
  <div>
    <list-header :list="list" :current="current" @change="changeCurrent"></list-header>
    <!-- <DT v-show="current == 0"></DT>
    <WZ v-show="current == 1"></WZ>
    <ZL v-show="current == 2"></ZL>
    <FD v-show="current == 3"></FD>
    <SCJ v-show="current == 4"></SCJ>-->
    <component :is="showComName"></component>
  </div>
</template>

<script>
import DT from './components/jueJinDemo/DT.vue';
import FD from './components/jueJinDemo/FD.vue';
import SCJ from './components/jueJinDemo/SCJ.vue';
import WZ from './components/jueJinDemo/WZ.vue';
import ZL from './components/jueJinDemo/ZL.vue';
import listHeader from './components/listHeader.vue';

export default {
  components: { listHeader, DT, FD, SCJ, WZ, ZL },
  data () {
    return {
      current: 0,
      list: ['动态', '文章', '专栏', '沸点', '收藏集'],
      ComList: ['DT', 'WZ', 'ZL', 'FD', 'SCJ'],
      showComName: 'DT'
    };
  },
  methods: {
    changeCurrent (index) {
      this.current = index
      this.showComName = this.ComList[index]
    }
  }
}
</script>

<style>
body {
  background-color: #f4f5f5;
}
</style>

2.0 组件缓存

使用动态组件是否会导致组件频繁的创建与销毁呢?

我们在各组件中的生命周期中进行一个以下的打印,查看我们切换的时候打印情况,来判断组件是否会销毁重新创建

 created () {
    console.log('动态被创建了');
  },
  destroyed () {
    console.log('动态组件被销毁了');
  }

可以发现,此时切换后打印效果如图

动画.gif

当我们使用动态组件进行切换时,每次切换都会使组件频繁的创建与销毁,这明显是性能低,当我们单个组件比较大的时候,会给浏览器带来较大的负担

我们不希望这种低性能的行为出现在我们的代码中,所以我们需要再用到vue为我们提供的<keep-alive>组件,也就是组件缓存的知识,让我们的组件不用每次频繁的创建与销毁

  • Vue内置的keep-alive组件,包裹起来要频繁切换的组件
  <keep-alive>
      <component :is="showComName"></component>
  </keep-alive>

此时我们再观察一下,组件还会不会频繁销毁

动画.gif

2.1关于组件缓存的两个扩展生命周期

当我们采用上述的切换方式时,会额外的触发两个生命周期,分别为activated,deactivated

  • activated当组件被激活展示中的时候会触发的生命周期
  • deactivated当组件从激活状态变为缓存状态时会触发的生命周期

同样是上面的Demo,我们为每个组件添加上下面的代码看看效果

  activated () {
    console.log('动态---被激活了');
  },
  deactivated () {
    console.log('动态---失去激活');
  }

动画.gif

我们可以在该什么周期中对该组件做一些初始化以及清除定时器等的一系列操作,详细可以参考我生命周期文章内的mounteddestroyed

3.0 组件插槽

当我们不确定组件内部数据与标签的时候应该怎么做?

哎嘿!这个时候我们就可以使用组件插槽的技术

通过<slot>标签可以让组件接受不同的标签结构展示

语法:

  • 组件内使用<slot></slot>占位
  • 使用组件标签内部插入我们要展示的标签结构

3.1 组件插槽实践

我们做一个弹窗,弹窗内部的内容不确定,保留弹窗的显影功能,作为插槽功能的实践

创建一个slotDemo文件夹,内部创建Dialog.vue组件

image.png

代码结构如下

<template>
  <div>
    <div class="pop" v-show="show" @click="changeShow"></div>
    <div class="dialog" v-show="show">
      <slot></slot>
    </div>
  </div>
</template>

<script>
export default {
  props: ['show'],
  methods: {
    changeShow () {
      this.$emit('change')
    }
  }
}
</script>

<style scoped>
.pop {
  background: rgba(0, 0, 0, 0.5);
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 90;
}

.dialog {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 300px;
  border-radius: 40px;
  background-color: #fff;
  padding: 40px;
  box-sizing: border-box;
  z-index: 99;
  display: flex;
  flex-direction: column;
  align-items: center;
}
</style>

此时我们可以就可以在App.vue中弹窗内插入我们想要插入的内容了,比如

<Dialog :show="isShow" @change="changeShow">
      <div class="title">标题</div>
      <div class="msg">测试测试测试</div>
    </Dialog>

或者在里面加入一个图片

   <Dialog :show="!isShow" @change="changeShow">
      <img
        src="https://p26-passport.byteacctimg.com/img/user-avatar/8601cadf9beb6e84bb3770d3b6baff7e~300x300.image"
        alt
      />
    </Dialog>

来看看效果吧

动画.gif

完整代码如下

<template>
  <div>
    <Dialog :show="isShow" @change="changeShow">
      <div class="title">标题</div>
      <div class="msg">测试测试测试</div>
    </Dialog>
    <Dialog :show="!isShow" @change="changeShow">
      <img
        src="https://p26-passport.byteacctimg.com/img/user-avatar/8601cadf9beb6e84bb3770d3b6baff7e~300x300.image"
        alt
      />
    </Dialog>
  </div>
</template>

<script>
import Dialog from './components/slotDemo/Dialog.vue';


export default {
  components: { Dialog },
  data () {
    return {
      isShow: true
    };
  },
  methods: {
    changeShow () {
      this.isShow = !this.isShow
    }
  }
}
</script>

<style>
body {
  background-color: #f4f5f5;
}
</style>

3.2 slot默认标签

假如使用标签我们不传递标签值那么展示什么呢

写上了slot确实让组件变得灵活了,同时也为我们带来了麻烦

比如一些弹窗我们就仅仅需要一个标题和文字就可以了,这种情况我们可以使用默认标签的方式来处理常规问题

默认标签的使用方法是 将需要默认展示的内容写在slot标签内即可,当调用组件的时候没有写入插槽内容,那么将展示默认内容

来将dialog组件做的更完善一些吧

<template>
  <div>
    <div class="pop" v-show="show" @click="changeShow"></div>
    <div class="dialog" v-show="show">
      <slot>
        <div class="title">{{title}}</div>
        <div class="msg">{{msg}}</div>
      </slot>
    </div>
  </div>
</template>

<script>
export default {
  props: ['show', 'title', 'msg'],
  methods: {
    changeShow () {
      this.$emit('change')
    }
  }
}
</script>

<style scoped>
.pop {
  background: rgba(0, 0, 0, 0.5);
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 90;
}

.dialog {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 300px;
  border-radius: 40px;
  background-color: #fff;
  padding: 40px;
  box-sizing: border-box;
  z-index: 99;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow: hidden;
}

.title {
  font-size: 20px;
  font-weight: 600;
  margin-bottom: 20px;
}

.msg {
  font-size: 16px;
}
</style>

此时我们将App.vue文件的第一个组件进行一些细微的修改

<Dialog :show="isShow" @change="changeShow" title="我是标题" msg="我是内容"></Dialog>

效果就出来了

动画.gif

3.3 具名插槽

当组件内有两处以上的不确定标签的时候我们有该怎么办?

如果写两个solt那么Vue又如何判断标签如何插入呢

可以给slot取一个名字吗?

答案是可以的,此时我们就要学习具名插槽的内容

语法:

  • slot使用name属性区分名字
      <slot name="title"></slot>
  • template配合v-slot或者#名字分发对应标签
<template v-slot:title>
        <h5>我是标题</h5>
</template>
or
<template #title>
        <h5>我是标题</h5>
</template>

此时我们将Dialog.vue文件中template部分进行修改

<template>
  <div>
    <div class="pop" v-show="show" @click="changeShow"></div>
    <div class="dialog" v-show="show">
      <slot name="title"></slot>
      <slot name="msg"></slot>
    </div>
  </div>
</template>

同时的App.vue中也做出对应修改

<template>
  <div>
    <Dialog :show="isShow" @change="changeShow" title="我是标题" msg="我是内容">
      <template v-slot:title>
        <h5>我是标题</h5>
      </template>
      <template v-slot:msg>
        <div>我是内容</div>
      </template>
    </Dialog>
    <Dialog :show="!isShow" @change="changeShow">
      <template #msg>
        <img
          src="https://p26-passport.byteacctimg.com/img/user-avatar/8601cadf9beb6e84bb3770d3b6baff7e~300x300.image"
          alt
        />
      </template>
    </Dialog>
  </div>
</template>

可以看到效果,使用具名插槽后我们的组件将更为精细化,可以解决更多需求

动画.gif

3.4 作用域插槽

当我们使用插槽的时候,能不能在插槽内使用组件内的变量呢?

解决此类问题我们通常会想到用组件通讯中的子传父之类的方法实现,亦或者ref的方式

但是,vue为我们提供了更为简便的方式----作用域插槽

语法

  • 子组件中使用:变量名绑定子组件内的变量值
  • 使用组件中,传入自定义标签,使用templatev-slot="自定义参数名"获取到作用域插槽的值

注意::变量名可以为任意名称的变量名由自己定义,但是需得和组件取的时候名称相同,v-slot="自定义参数名"后的名称也为自定义,当然我们通常会采用:rowv-slot="scope"来作为参数名

对代码修改一下,测试一下效果

Dialog.vue修改如下

<template>
  <div>
    <div class="pop" v-show="show" @click="changeShow"></div>
    <div class="dialog" v-show="show">
      <!-- <slot name="title"></slot> -->
      <slot :row="defalutMsg"></slot>
    </div>
  </div>
</template>

<script> 
export default {
  props: ['show', 'title', 'msg'],
  methods: {
    changeShow () {
      this.$emit('change')
    }
  },
  data () {
    return {
      defalutMsg: '法外狂徒张三'
    }
  }
}
</script>

<style scoped>
.pop {
  background: rgba(0, 0, 0, 0.5);
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 90;
}

.dialog {
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 300px;
  height: 300px;
  border-radius: 40px;
  background-color: #fff;
  padding: 40px;
  box-sizing: border-box;
  z-index: 99;
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow: hidden;
}

.title {
  font-size: 20px;
  font-weight: 600;
  margin-bottom: 20px;
}

.msg {
  font-size: 16px;
}
</style>

App.vue修改如下

<template>
  <div>
    <Dialog :show="isShow" @change="changeShow" title="我是标题" msg="我是内容">
      <template v-slot="scope">
        <p>{{scope.row}}</p>
      </template>
    </Dialog>
    <Dialog :show="!isShow" @change="changeShow">
      <img
        src="https://p26-passport.byteacctimg.com/img/user-avatar/8601cadf9beb6e84bb3770d3b6baff7e~300x300.image"
        alt
      />
    </Dialog>
  </div>
</template>

<script>
import Dialog from './components/slotDemo/Dialog.vue';


export default {
  components: { Dialog },
  data () {
    return {
      isShow: true
    };
  },
  methods: {
    changeShow () {
      this.isShow = !this.isShow
    }
  }
}
</script>

<style>
body {
  background-color: #f4f5f5;
}
</style>

效果如图 动画.gif

4.0 组件props详解

在之前的组件通讯文章中有介绍到props,是组件传值的一个通道,可供父组件直接传递值的一些参数将在子组件中定义在props中

我们之前学习的是以数组字符串的形式去接受传递过来的props值

这种定义形式无法规范组件使用者传递过来的值的类型与自定义默认值

现在我们需要学习一种对于组件更为严谨的定义props的形式

当我们需要规范组件调用者传递参数的类型或者给props一个默认值的时候我们需要使用如下的方式定义props

  props: {
    msg: {
      type: String,
      default: '平平无奇小天才'
    },
    list: {
      type: Array,
      default: () => [6, 6, 6]
    },
    test: {
      type: String
    }
  }

语法

  • props变为一个大的对象了而非数组
  • 参数语法为 参数名自定义, type中定义类型值,默认值字符串等简单数据类型直接赋值即可,而数组对象需要使用一个箭头函数返回默认值,当组件调用者没有传递该参数的时候,则默认展示默认值,默认值和类型可以不定义,但参数必须是对象类型
参数名: {
    类型值: 类型,
    默认值: '默认的参数值'
}

我们做一个小的Demo实践一手 创建组件PropsDemo.vue 代码如下

<template>
  <div>
    <h1>{{msg}}</h1>
    <ul>
      <li v-for="(item,index) in list" :key="index">{{item}}</li>
    </ul>
    <p>{{test}}</p>
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: '平平无奇小天才'
    },
    list: {
      type: Array,
      default: () => [6, 6, 6]
    },
    test: {
      type: String
    }
  }
}
</script>

<style>
</style>

App.vue中引用并仅传入test值,用vsCode快捷导入即可,这里就不贴完整代码了

 <PropsDemo test="测试"></PropsDemo>

效果如图

image.png

可以发现当我们没有传递值的时候默认值依旧生效,基于此我们可以更为完善我们组件的功能,实际开发也更为推荐更多的使用此方法来定义props

4.1 子组件如何直接修改props值

没做处理的情况下,子组件是不能直接修改props的值的,因为props的是由父组件传递过来的值,通常情况下子组件的修改并不会带动父组件的修改,此时就会出现数据不同步的报错

常规处理是在子组件通过this.$emit()调用父组件的处理方法,这种处理方式比较麻烦的是,我们每次使用该组件都要写很多的处理方法,组件是复用的但是修改方法却没有封装在组件内

Vue是一个MVVM框架,假如我们给props值一个双向绑定呢?可以不可以实现直接在子组件修改props值呢

答案是可以的,而且这种方法有两种,我都放在这章里了,想要就去拿吧!

4.1.1 使用v-model实现

我们知道,v-model是实现数据双向绑定的,通常使用在受控组件也就是比如说input等可以进行输入的组件,同样的,自定义组件也可以绑定v-model,自定义组件默认绑定的props属性为value

使用方式

  • 父组件中使用v-model绑定需要子组件直接修改的值
  • 子组件接受名为valueprops属性
  • 子组件通过this.$emit('input',传递的值)对父组件的状态直接进行修改

同样的使用之前的PropsDemo.vue文件,我们进行一个实践

<template>
  <div>
    <h1>{{msg}}</h1>
    <ul>
      <li v-for="(item,index) in list" :key="index">{{item}}</li>
    </ul>
    <p>{{test}}</p>
    <!-- 新增修改属性的入口 -->
    <input type="text" v-model="txtValue" @input="changeValue" />
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: '平平无奇小天才'
    },
    list: {
      type: Array,
      default: () => [6, 6, 6]
    },
    test: {
      type: String
    },
    // 接受props
    value: {
      type: String
    }
  },
  data () {
    return {
      txtValue: ''
    }
  },
  methods: {
    changeValue (e) {
      // 调用修改方法
      this.$emit('input', this.txtValue)
    }
  }
}
</script>

<style>
</style>

父组件中做一个v-model的绑定与调用,App.vue文件中

<template>
  <div>
    <PropsDemo v-model="fatherValue"></PropsDemo>
    <button @click="showValue">打印父组件value</button>
  </div>
</template>

<script>
import PropsDemo from '../src/components/propDemo/PropsDemo.vue'
export default {
  components: {
    PropsDemo
  },
  data () {
    return {
      fatherValue: ''
    }
  }, methods: {
    showValue () {
      console.log(this.fatherValue);
    }
  }
}
</script>

<style>
</style>

实现效果如图

动画.gif

可以父组件并没有绑定处理事件,就直接让子组件修改了父组件的数据

重点注意:修改不是直接对value进行修改那样会报错,调用this.$meit('input',值)来修改,v-model已经为我们处理好了该类事件

4.1.2 使用.sync修饰符进行修改

上面使用的v-model指令实现的绑定有两个没那么方便的点

  1. 默认绑定的value属性,比较局限
  2. 一个组件仅可绑定一个v-model指令

这两个问题使用.sync将会得到解决,可以为组件绑定多个可供子组件直接修改的属性,你与此仅缺少一个修饰符

使用语法

  • 父组件传递属性时后缀加上.sync修饰符 如 :属性名.sync="父组件的属性"
  • 子组件接受该属性并调用修改方法 this.$emit('update:属性名', 修改后的值)

v-mode本质上区别不大,我们做一些小的修改即可,来个实践感受一下

PropsDemo.vue修改为

<template>
  <div>
    <h1>{{msg}}</h1>
    <ul>
      <li v-for="(item,index) in list" :key="index">{{item}}</li>
    </ul>
    <p>{{test}}</p>
    <!-- 新增修改属性的入口 -->
    <input type="text" v-model="txtValue" @input="changeValue" />
  </div>
</template>

<script>
export default {
  props: {
    msg: {
      type: String,
      default: '平平无奇小天才'
    },
    list: {
      type: Array,
      default: () => [6, 6, 6]
    },
    test: {
      type: String
    },
    // 接受props
    value: {
      type: String
    }
  },
  data () {
    return {
      txtValue: ''
    }
  },
  methods: {
    changeValue (e) {
      // 调用修改方法
      this.$emit('update:value', '测试修改字符串')
      this.$emit('update:test', this.txtValue)
    }
  }
}
</script>

<style>
</style>

App.vue中更为如下

<template>
  <div>
    <PropsDemo :value.sync="fatherValue" :test.sync="fatherTest"></PropsDemo>
    <button @click="showValue">打印父组件value</button>
  </div>
</template>

<script>
import PropsDemo from '../src/components/propDemo/PropsDemo.vue'
export default {
  components: {
    PropsDemo
  },
  data () {
    return {
      fatherValue: '',
      fatherTest: ''
    }
  }, methods: {
    showValue () {
      console.log(this.fatherValue, this.fatherTest);
    }
  }
}
</script>

<style>
</style>

此时我们可以看到效果如图

动画.gif

5.0 Vue自定义指令

我们现如今以及学习了很多的指令,如v-for,v-bind等等,这些指令让我们码代码的时候少些很多重复的js逻辑,那么我们可不可以自己也封装一些指令在Vue中使用呢?

本质上指令就是将一锻js逻辑代码挂载在vue全局,实现了不同的效果,如v-for就是使用了遍历数组的方法,类似的React中没有这种语法糖,都是直接书写js代码来实现逻辑复用的

data.map((item,index)=>{ return <li key={index}>{item}</li> })

Vue允许我们自定义一系列的指令进行使用,在原有的基础上拓展额外的功能,分为全局和局部注册两种,局部注册只能在当前文件中使用,全局注册的指令可以在任意组件中使用

语法:

  • 全局注册 main.js
Vue.directive("指令名",{
  "inserted"(el){
    console.log(el);
  }
})
  • 局部注册
  directives: {
    "指令名称": {
      inserted(el){
        console.log(el);
      }
    }
  }

inserted方法: 指令所在标签被渲染为真实Dom时,会触发,参数el为当前指令所在的标签Dom

随后在标签中使用指令即可, 语法为v-指令名

我们制作一个简单的聚焦指令,校验我们所学

main.js中添加


Vue.directive("focus",{
  "inserted"(el){
    console.log(el);
    el.focus()
  }
})

或者在App.vue中添加

  directives: {
    "focus": {
      inserted (el) {
        console.log(el);
        el.focus()
      }
    }
  }

都可以实现自定义指令的效果,不同的是main.js为全局注册,所有页面都可以使用该指令,而App.vue中是局部指令,仅可以在当前页面中使用

最后在页面中使用一下指令

<input type="text" v-focus />

可以看到效果如图

image.png

实现了聚焦以及打印的效果

5.1 自定义指令的钩子函数

自定义指令也有自己的钩子函数,类似于组件的生命周期,会在绑定的元素不同状态实现不同的效果,并且,这种钩子函数在从vue2到vue3这段时间又有了比较大的改动

本文实践暂且使用的vue2环境,当然也会介绍vue3环境的钩子函数有何不同

客官根据自己的vue版本浅尝即可

优先介绍一些vue2版本自定义指令的钩子函数

  • bind 通过字面意思就可以理解为绑定,当指令绑定在元素后调用,也是最先调用的钩子函数,仅调用一次
  • inserted 当元素插入父Dom后调用,注意需要绑定的元素渲染为真实的Dom,如果有v-if等该Dom未渲染,那么指令也不会调用
  • update 当元素更新前调用(当元素更新,子元素未更新时)
  • componentUpdated 当元素更新后调用(组件和子元素都更新时)
  • unbind 一旦指令被移除,将会调用次钩子,同样的仅调用一次

那么我们再来看看vue3版本有有何改变吧

  • created 新增的钩子函数,在元素的 attribute 或事件监听器被应用之前调用
  • beforeMount 原先的bind钩子函数更为此钩子,可以理解为挂载前
  • mounted 原先的inserted钩子函数更为此钩子,可以理解为挂载后,基本上都在此钩子中写入自定义函数的方法逻辑
  • beforeUpdate 新增的钩子函数,更新前调用的钩子函数,与生命周期中的极其类似
  • updated update已移除,两个钩子函数有些重复了,所以原本update改用为本钩子函数updated
  • beforeUnmount 新增的钩子函数,在元素卸载之前调用
  • unmounted unbind更为unmounted当元素卸载或指令被移除之后调用

其实自定义组件的生命周期和vue3版本的自定义指令的生命周期已经极其类似了,这也是我觉得vue3做的比较好的点,比起原先的钩子更容易理解了,同时也给了coding们更多的操作空间

5.2 自定义指令的参数

每一个自定义指令通常包含了四个钩子函数,分别为el,binding,vnode,oldVnode

  • el 指令绑定的元素,用于直接操作Dom
  • binding 指令相关的一些属性集合,包含如下
    • name 不含v-前缀的指令名,即v-focus的name为focus
    • value 指令的绑定值(自定义指令后是可以绑定值的 即v-focus = "6*6" value则为36
    • oldValue 指令之前绑定的值,有点类似于侦听器的oldVal,仅在updatecomponentUpdatedbeforeUpdateupdated中生效
    • expression 字符串形式的表达式 即v-focus = "6*6" expression则为"6*6"
    • arg 传给指令的参数,即v-focus : click arg则为click
    • modifiers 指令的修饰符对象 , 即 v-focus.click那么,修饰符对象为:{click:true}
  • vnode Vue编译生成的虚拟节点
  • oldVnode 上一个虚拟节点,仅在updatecomponentUpdatedbeforeUpdateupdated中生效

随着业务需求活学活用参数与钩子函数,就可以做出一些理想的自定义组件啦

来看看我的其他章节吧,正在长更中

从0到Vue3企业项目实战【01.Vue的基本概念与学习指南】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【02.了解并理解Vue指令以及虚拟Dom】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【03.vue基本api入门】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【04.从vue组件通讯到eventBus以及vuex(附mock接口与axios简单实践)】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【05.vue生命周期】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【06.refref与nextTick使用】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【07.vue动态组件,组件缓存,组件插槽,子组件直接修改props,自定义指令看这一篇就够了】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【08.Vue路由基础】 - 掘金 (juejin.cn)

从0到Vue3企业项目实战【09.Vue路由进阶】 - 掘金 (juejin.cn)