🌵「硬核Vue」Vue的高级特性你真的会用吗

691 阅读6分钟

「本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

写在前面

如今身边学习Vue的人越来越多了,其中也包括一些后端的同学,Vue自发布以来已经成功在前端领域占据一席之地,Vue相比于Angular这种笨重的框架,它的特点是小而清,另外,与React相比,上手容易。官方的定义是一套用于构建用户界面的渐进式框架

笔者理解的渐进式框架也非常简单,就是用你想用或者能用的功能特性,你不想用的部分功能可以先不用。Vue不强求你一次性接受并使用它的全部功能特性。

由此可见,Vue的确是容易上手的一个框架,然而Vue中还有一些高级功能,笔者总结出了Vue的一些高级特性,目的是帮助学习者掌握这些Vue的高级技巧,帮助读者从面试或工作中脱颖而出。

Vue 高级特性:

  • 这里所介绍的高级特性是基于Vue2的,关于Vue3的更新会在后面的文章中作介绍

  • (1)自定义V-model (2)$nextTick (3)slot

  • (4)动态、异步组件 (5)keep-alive (6)mixin

1. 自定义V-model

即在组件上使用v-model命令;使用场景:使用一个颜色选择器组件,或者封装一个自己公司项目的 Input ,要求输入一些项目特定的号码或者文字。此时你就不能用最简单的 Input 了,得自己封装一个 CustomInput 。

CustomVmodel组件代码:

<template>
  <div>
    <input type="text":value="text1"
    @input="$emit('change1',$event.target.value)">
    <!-- 
      1. 上面的input使用了 :value 而不是 v-model
      2. 上面的change1和model.event1要对应起来
      3. text1属性要对应起来
    -->
  </div>
</template>
<script>
export default {
  model:{
    prop:'text1',// 对应props里面的属性
    event:'change1'
  },
  props:{
    text1:String,
    default(){
      return ''
    }
  }
}
</script>

接下来使用这个组件:

<template>
  <div>
    <p>高级特性</p>
    <p>{{name}}</p>
    <!--自定义v-model-->
    <CunstomVmodel v-model="name"/>
  </div>
</template>
<script>
import CunstomVmodel from './ComponentVmodel.vue'// 引入组件
export default {
  components:{
    CunstomVmodel// 注册组件
  },
  data(){
    return{
      name:'橙子的前端游乐园'
    }
  }
}
</script>
<style>
body{
  background-color: rgb(176, 138, 226);
}
  p:nth-child(2){
    color: lightgreen;
  }
</style>

代码演示:

20210706_151322.gif 奈斯!这样我们就成功在自定义的组件上使用v-model指令啦😄。

2. $nextTick

首先,我们要清楚Vue是异步渲染的,React也是。异步渲染的意思是data改变之后,DOM不会立刻渲染。如果我们想在数据改变之后立即获取“正确的DOM节点”,我们需要调用$nextTick,nextTick会在DOM渲染之后被触发,以获取最新的DOM节点。 **通过下面的Demo你将会对\nextTick有更加清楚的认识
**

$nextTick组件代码:

<template>
  <div>
    <ul ref="ul1">
      <li v-for="(item,index) in fruits" :key="index">{{item}}</li>
    </ul>
    <button @click="addItem">add</button>
  </div>
</template>
<script>
export default {
  data(){
    return{
      fruits:['橙子','柚子','菠萝']
    }
  },
  methods:{
    addItem(){
      this.fruits.push(`${Date.now()}`)
      this.fruits.push(`${Date.now()}`)
      this.fruits.push(`${Date.now()}`)
      const ulElem = this.$refs.ul1
      console.log(ulElem.childNodes.length);
    }
  }
}
</script>

解释一下这段代码:点击add按钮,获取ul节点,并打印ul的子节点的长度(即li的个数) 20210706_154520.gif 我们发现第一次添加数据之后,打印节点的长度应该是6,控制台打印的结果却是3,这正是Vue异步渲染的原因,数据改变之后,DOM不会立刻渲染。如果想在数据改变之后立刻获取到“正确的DOM”,我们就需要调用$nextTick

<template>
  <div>
    <ul ref="ul1">
      <li v-for="(item,index) in fruits" :key="index">{{item}}</li>
    </ul>
    <button @click="addItem">add</button>
  </div>
</template>
<script>
export default {
  data(){
    return{
      fruits:['橙子','柚子','菠萝']
    }
  },
  methods:{
    addItem(){
      this.fruits.push(`${Date.now()}`)
      this.fruits.push(`${Date.now()}`)
      this.fruits.push(`${Date.now()}`)
      // 1.异步渲染,$nextTick待DOM渲染完再回调
      // 2.页面渲染时会将data的修改做整合,多次data修改只会渲染一次
    this.$nextTick(()=>{
      const ulElem = this.$refs.ul1
      console.log(ulElem.childNodes.length);
    })
    }
  }
}
</script>

代码演示:

20210706_155554.gif 奈斯!我们通过$nextTick实现了改变数据后,立即获取DOM节点的功能😋。

3. slot

slot的知识点偏多,主要有基本使用(基础知识点)、作用域插槽和具名插槽。

基本使用

子组件代码:

<template>
  <div>
<a :href="url">
      <slot>
        这里是默认内容
    </slot>
</a>
  </div>
</template>
<script>
export default {
  props:['url'],
  data(){
    return{}
  }
}
</script>

子组件中我们通过props获取父组件传递过来的url,将url给a标签,然后在a标签中定义了一个插槽,这个插槽的作用就是存放父组件中写在该子组件里的内容即{{webiste.title}}(百度一下),如果内容为空则显示默认内容。

根组件代码:

<template>
  <div>
    <p>高级特性</p>
    <p>{{name}}</p>
    <SlotDemo :url="webiste.url">
      {{website.title}}
    </SlotDemo>
  </div>
</template>
<script>
import SlotDemo from './Slot.vue'
export default {
  components:{
    SlotDemo
  },
  data(){
    return{
      name:'橙子的前端游乐园',
      website:{
        url:'www.baidu.com',
        title:'百度一下'
      }
    }
  }
}
</script>
<style>
body{
  background-color: rgb(176, 138, 226);
}
  p:nth-child(2){
    color: lightgreen;
  }
</style>

image.png 看!这样我们就成功地往插槽中插入了数据🙃。

作用域插槽

作用域插槽存在的主要作用就是子组件把data里的值扔出来让父组件获取到。作用域插槽不是很好理解,我们直接看代码部分吧。

ScopedSlot子组件代码:

<template>
  <div>
    <a href="url">
      <slot :slotData='website'>// 这里的slotData是随意命名的,在父组件接收即可

      </slot>
    </a>
  </div>
</template>
<script>
export default {
  props:['url'],
  data(){
    return{
      website:{
        url:'https://www.jd.com/',
        title:'京东购物'
      }
    }
  }
}
</script>

根组件代码:

<template>
  <div>
    <p>高级特性</p>
    <p>{{name}}</p>
    <ScopedSlot :url="website.url">
    <!-- 这里的template是必写的,然后slotprops是随意命名的-->
      <template v-slot="slotprops">
        {{slotprops.slotData.title}}
      </template>
    </ScopedSlot>
  </div>
</template>
<script>
import ScopedSlot from './ScopedSlot.vue'
export default {
  components:{
    ScopedSlot
  },
  data(){
    return{
      name:'橙子的前端游乐园',
      website:{
        url:'www.baidu.com',
        title:'百度一下'
      }
    }
  }
}
</script>
<style>
body{
  background-color: rgb(176, 138, 226);
}
  p:nth-child(2){
    color: lightgreen;
  }
</style>

代码演示:

image.png 看!我们在跟组件中获取到了子组件的数据并把它插入到子组件的插槽中,这就是作用域插槽存在的意义😝!

具名插槽

存在多个solt给每个slot取一个名字,大家直接看代码吧。

NamedSlot组件代码:

<template>
  <div>
  <h1><slot name='slot1'></slot></h1>
  <h2><slot></slot></h2>
  <h3><slot name='slot2'></slot></h3>
  </div>
</template>
<script>
export default {
  
}
</script>

根组件代码:

<template>
  <div>
    <p>高级特性</p>
    <p>{{name}}</p>
    <NamedSlot>
      <template v-slot:slot1>
        我是h1,将插入到slot1中
      </template>
      <template v-slot:slot2>
        我是h2,将插入到slot2中
      </template>
      <p>我是p,将插入到未命名的slot中</p>
    </NamedSlot>
  </div>
</template>
<script>
import NamedSlot from './NamedSlot.vue'
export default {
  components:{
    NamedSlot
  },
  data(){
    return{}
  }
}
</script>
<style>
body{
  background-color: rgb(176, 138, 226);
}
  p:nth-child(2){
    color: lightgreen;
  }
</style>

代码演示:

image.png 看,我们把数据插入到想插入的插槽中,这也就是具名插槽的作用啦😊。

4.动态、异步组件

:is = "component-name"用法;需要根据数据动态渲染的场景,即组件类型不确定

动态组件

比如说你需要开发一个新闻详情页,你只知道数据,不知道组件渲染的顺序 image.png
我们现在来实现一下这个功能:
根组件代码:

<template>
<div>
    <div v-for="(val,key) in comname" :key='key'>
    <component :is="val.type"></component>
  </div>
</div>
</template>
<script>
import MyText from './Text.vue'
import MyImage from './Image.vue'
export default {
    components:{
    MyText,
    MyImage
  },
  data(){
    return{
      comname:{
        1:{
          type:'MyText'
        },
        2:{
          type:'MyText'
        },
        3:{
          type:'MyImage'
        },
      }
    }
  }
}
</script>

子组件代码:

<template>
  <div>
    <p>我是Text组件</p>
  </div>
</template>
<script>
export default {
  data(){
    return{}
  }
}
</script>
<template>
  <div>
    <p>我是Image组件</p>
  </div>
</template>
<script>
export default {
  
}
</script>

代码演示:

image.png

异步组件

异步组件存在的目的就是按需加载,比如一些比较大的组件,如Echart图标,或是代码编辑器这种,一开始就加载会非常影响性能。注册组件时会使用import()函数来引入 根组件代码:

<template>
  <div>
    <p>高级特性</p>
    <p>{{name}}</p>
    <AsyncComp v-if="isShow"/>
    <button @click="showComp">show AsyncComp</button>
  </div>
</template>
<script>
export default {
  components:{
     AsyncComp:()=>import('./AsynComponent.vue')
  },
  data(){
    return{
      name:'橙子的前端游乐园',
      website:{
        url:'www.baidu.com',
        title:'百度一下'
      },
      isShow:false
    }
  },
  methods:{
    showComp(){
      this.isShow=!this.isShow
    }
  }
}
</script>
<style>
body{
  background-color: rgb(176, 138, 226);
}
  p:nth-child(2){
    color: lightgreen;
  }
</style>

AsynComponent代码

<template>
  <div>
     <p>输入框:</p>
    <input type="text" v-model.trim="name">
    <!-- <input type="text" v-model.lazy="name">
    <input type="text" v-model.number="age"> -->
    <p>多行文本</p>
    <textarea  cols="30" rows="10" v-model="text"></textarea>
    <p>复选框</p>
    <input id="橙子" type="checkbox" v-model="checked1">
    <label for="橙子">橙子</label>
    <input id="柚子" type="checkbox" v-model="checked2">
    <label for="柚子">柚子</label>
    <input id="菠萝" type="checkbox" v-model="checked3">
    <label for="菠萝">菠萝</label>
    <p>单选框</p>
    <input type="radio" id="male" value="male" v-model="gender">
    <label for="male"></label>
    <input type="radio" id="female" value="female" v-model="gender">
    <label for="female"></label>
    <p>下拉列表选择(单选)</p>
    <select name="" id="" v-model="selected">
      <option disabled value="">请选择</option>
      <option value="">A</option>
      <option value="">B</option>
      <option value="">C</option>
      <option value="">D</option>
    </select>
    <p>下拉列表选择(多选)</p>
    <select name="" id="" v-model="selectedList" multiple>
      <option disabled value="">请选择</option>
      <option value="A">A</option>
      <option value="B">B</option>
      <option value="C">C</option>
      <option value="D">D</option>
    </select>
  </div>
</template>
<script>
export default {
    data(){
    return {
      name:'橙子',
      age:18,
      text:'1314520',
      checked1:true,
      checked2:false,
      checked3:true,6
      gender:'male',
      selected:'',
      selectedList:[]
    }
  }
}
</script>

代码演示:

20210706_191812.gif 我们会发现只有点击按钮,要显示该组件的时候才会引入该组件并渲染,大大提升了性能,这就是异步组件的作用——按需加载👍。

5. keep-alive

缓存组件;适用于频繁切换,不需要重复渲染的场景,例如Tab栏切换。我们通过代码演示看看如何使用。

根组件代码:

<template>
  <div>
    <p>高级特性</p>
    <p>{{name}}</p>
    <KeepAlive/>
  </div>
</template>
<script>
import KeepAlive from './KeepAlive.vue'
export default {
  components:{
    KeepAlive
  },
  data(){
    return{
      name:'橙子的前端游乐园',
    }
  }
}
</script>
<style>
body{
  background-color: rgb(176, 138, 226);
}
  p:nth-child(2){
    color: lightgreen;
  }
</style>

KeepAlive组件代码:

<template>
  <div>
    <button @click="changeState('A')">A</button>
    <button @click="changeState('B')">B</button>
    <button @click="changeState('C')">C</button>
      <KeepAliveA v-if="state==='A'"/>
      <KeepAliveB v-if="state==='B'"/>
      <KeepAliveC v-if="state==='C'"/>
  </div>
</template>
<script>
import KeepAliveA from './KeepAliveStateA.vue'
import KeepAliveB from './KeepAliveStateB.vue'
import KeepAliveC from './KeepAliveStateC.vue'
export default {
  components:{
    KeepAliveA,
    KeepAliveB,
    KeepAliveC
  },
  data(){
    return{
      state:'A'
    }
  },
  methods:{
    changeState(state){
      this.state=state
    }
  }
}
</script>

KeepAliveA(B,C)代码:

<template>
  <div>
    This is A 组件。
  </div>
</template>
<script>
export default {
  mounted() {
    console.log('A is mounted')
  },
  destroyed(){
    console.log('A is destroyed')
  }
}
</script>

代码演示:

20210706_212551.gif

我们发现在切换组件的时候,组件都有被销毁,这样特别消耗性能,我们需要将组件缓存起来。

image.png

我们有keepalive将这些组件包裹起来了,再来看效果

20210706_213216.gif

看!我们通过keepalive标签成功地将组件缓存起来了,优化了性能,就很奈斯哦😀!

6. Mixin

多个组件有相同的逻辑,抽离出来;Mixin不是完美的方案会有一些方案,在Vue3.0中,提出的Composition API解决了这些问题。我们看代码部分了解一些什么是Mixin吧~ Mixin组件代码:

<template>
  <div>
    <p>我的名字是{{name}}</p>
    <p>{{age}}</p>
    <p @click="showPicture">This is my picture</p>
  </div>
</template>
<script>
  import myMixin from './Mixin'
export default {
  mixins:[myMixin],// 可以添加多个
  data(){
    return{
        name:'橙子'
    }
  },
  mounted(){
    console.log('名字 is mounted');
  }
}
</script>

代码演示:

image.png 我们会发现页面多出了18,控制台多出了几条生疏的代码。这是从哪来的呢?答案就是mixn。 我们定义了Mixin.js的文件

export default {
  data() {
    return {
      age: 18,
    }
  },
  methods: {
    showPicture() {
      console.log("picture is here")
    },
  },
  mounted() {
    console.log("Mixin is mounted")
  },
}

mixin就是把这些共同的逻辑抽离出来,然后通过mixns引入使用,避免了重复定义。

Mixn存在的一些问题:

  1. 变量来源不明确,不利于阅读
  2. 多mixin可能会造成命名冲突。
  3. mixin和组件可能组件出现多对多的关系,复杂度较高。

最后

本文总结了Vue中的高级特性,虽然你在工作中不一定能用到,但是正所谓技多不压身,掌握这些技巧也许就有发挥作用的时候呢,我们要清楚各种特性的应用场景,相信通过学习积累,读者一定能玩转Vue啦~
如果这篇文章对你有帮助的话,麻烦点赞收藏哟~
GitHub 博客地址: github.com/skyblue309
笔者还有其他专栏,欢迎阅读~
Vue从放弃到入门
深入浅出JavaScript
玩转CSS之美