Vue3笔记(六):ref属性、nextTick、自定义指令、自定义全局属性、Mixin、插件

151 阅读3分钟

ref属性在元素和组件上的分别使用

ref属性

前面我们介绍过,Vue是基于MVVM设计模式进行实现的,视图与数据不直接进行通信,但是Vue并没有完全遵循这一原则,而是允许开发者直接进行原生DOM操作。

在Vue中可通过ref属性来完成这一行为,通过给标签添加ref属性,然后再通过vm.$refs来获取DOM,代码如下:

<template>
  <div>
    <h2>ref属性</h2>
    <button @click="handleClick">点击</button>
    <div ref="elem">aaaaaaaaa</div>
    <div ref="elem2">bbbbbbbbb</div>
  </div>
</template>

<script>
  export default {
    methods: {
      handleClick(){
        // ref属性添加到元素身上,可以获取到当前元素的原生DOM
        console.log( this.$refs.elem );
        console.log( this.$refs.elem2 );
      }
    }
  }
</script>

除了可以把ref属性添加给DOM元素外,还可以把ref属性添加给组件,这样可以获取到组件的实例对象,可以间接的实现组件之间的通信,代码如下:

<template>
  <div>
    <h2>ref属性</h2>
    <my-head ref="elem3"></my-head>
  </div>
</template>

<script>
  import MyHead from '@/2_头部组件.vue'
  export default {
    methods: {
      handleClick(){
        // ref属性添加到组件身上,可以获取到当前组件的vm对象(实例对象)
        console.log( this.$refs.elem3 );
        console.log( this.$refs.elem3.message );
        this.$refs.elem3.handleMessage('根组件的数据');
        //$refs 也可以实现间接的父子通信
      }
    }
  }
</script>

2_头部组件.vue文件:

<template>
  <div>
    hello myhead
  </div>
</template>

<script>
  export default {
    data(){
      return {
        message: '头部组件的消息'
      }
    },
    methods: {
      handleMessage(data){
        console.log(data);
      }
    }
  }
</script>

利用nextTick监听DOM更新后的情况

本小节我们将学习一下nextTick方法,它的主要作用是将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。

默认情况下,数据的更新会产生一个很小的异步延迟,所以直接再数据改变后取获取DOM是得不到DOM更新后的结果,而得到的是DOM更新前的结果。

<template>
  <div>
    <h2>hello nextTick</h2>
    <div ref="elem">{{ message }}</div>
  </div>
</template>

<script>
  export default {
    data(){
      return {
        message: 'hello world'
      }
    },
    mounted(){
      setTimeout(()=>{
        this.message = 'hi vue';
        console.log( this.$refs.elem.innerHTML );  // 'hello world'
       
      }, 2000)
    }
  }
</script>

如何才能得到DOM更新后的结果呢,可以有两种方案,第一种就是利用生命周期updated这个钩子函数,第二种就是利用我们讲的nextTick方法,支持两种风格:回调和promise。

<template>
  <div>
    <h2>hello nextTick</h2>
    <div ref="elem">{{ message }}</div>
  </div>
</template>

<script>
  export default {
    data(){
      return {
        message: 'hello world'
      }
    },
    mounted(){
      setTimeout(()=>{
        this.message = 'hi vue';
        /* this.$nextTick(()=>{
          console.log( this.$refs.elem.innerHTML );   // 'hi vue'
        }) */
        this.$nextTick().then(()=>{
          console.log( this.$refs.elem.innerHTML );  // 'hi vue'
        })
      }, 2000)
    },
    updated(){
       console.log( this.$refs.elem.innerHTML );  // 'hi vue'
    }
  }
</script>

自定义指令与自定义全局属性及应用场景

除了核心功能默认内置的指令 (例如 v-model 和 v-show),Vue 也允许注册自定义指令,来实现一些封装功能。

自定义指令的实现

首先我们先来实现一个简单的v-color指令,用于给元素添加背景色,代码如下:

<template>
  <div>
    <h2>自定义指令</h2>
    <div @click="handleClick" v-color="color">aaaaaaa</div>
  </div>
</template>

<script>
  export default {
    data(){
      return {
        color: 'red'
      }
    }, 
    //创建局部的自定义指令
    directives: {
      /* color: {
        mounted(el, binding){
          el.style.background = binding.value
        },
        updated(el, binding){
          el.style.background = binding.value
        }
      } */
      color: (el, binding) => {
        el.style.background = binding.value
      }
    }
  }
</script>

这里的回调函数是指令中mounted生命周期和updated生命周期的简写方式。

下面我们来完成一个实际可以应用的指令,按钮权限指令,一般情况下这种指令不会局部使用,而是全局使用,所以可以通过vue来实现一个全局的按钮权限指令,代码如下:

// main.js
app.directive('auth', (el, binding) => {
    let auths = ['edit', 'delete'];
    let ret = auths.includes(binding.value);
    if(!ret){
    	el.style.display = 'none';
    }
});

// demo.vue
<template>
<button v-auth="'edit'">编辑</button>
</template>

自定义全局属性

添加一个可以在应用的任何组件实例中访问的全局 property,这样在引入一些第三方模块的时候,就不用每一次进行import操作,而是直接通过this对象去访问全局属性即可,下面举一个例子,实现一个http的全局属性。

// http.js
function get() {
    console.log('get request');
}

export {
    get
}

// vue
import { get } from '@/http.js'
export default {
    methods: {
        handleClick() {
            get();
        }
    }
}
// main.js
app.config.globalProperties.$http = http;

//demo.vue
<script>
export default {
   created(){
       this.$http.get();
   }
}
</script>

复用组件功能之Mixin混入

Mixin混入

本小节我们来了解一下mixin混入,它是选项式API的一种复用代码的形式,可以非常灵活的复用功能。

// mymixin.js
const myMixin = {
  data(){
    return {
      message: '复用的数据'
    }
  },
  computed: {
    message2(){
      return '复用的数据2'
    }
  }
};
export {
  myMixin
}
// mymixin.vue
<template>
  <div>
    <h2>mixin混入</h2>
    <div>{{ message }}</div>
    <div>{{ message2 }}</div>
  </div>
</template>
<script>
  import { myMixin } from '@/mymixin.js'
  export default {
    mixins: [myMixin]
  }
</script>

这样就可以直接在.vue中使用这些混入的功能。当然这种方式是局部混入写法,也可以进行全局混入的写法,代码如下:

// main.js
import { myMixin } from '@/mymixin.js'
app.mixin(myMixin)

mixin存在的问题也是有的,那就是不够灵活,不支持传递参数,这样无法做到差异化的处理,所以目前比较推荐的复用操作还是选择使用组合式API中的use函数来完成复用的逻辑处理。后面章节我们会学习到这种组合式API的应用。

插件的概念及插件的实现

插件是自包含的代码,通常向 Vue 添加全局级功能。例如:全局方法、全局组件、全局指令、全局mixin等等。基于Vue的第三方模块都是需要通过插件的方式在Vue中进行生效的,比如:Element Plus、Vue Router、Vuex等等。

// myplugin.js
import * as http from '@/http.js'
export default {
  install(app, options){
    console.log(options);  // {info: '配置信息'}
    app.config.globalProperties.$http = http;
    app.directive('auth', (el, binding) => {
      let auths = ['edit', 'delete'];
      let ret = auths.includes(binding.value);
      if(!ret){
        el.style.display = 'none';
      }
    });
    app.component('my-head', {
      template: `<div>hello myhead</div>`
    })
  }
}
// main.js 让插件生效
import myplugin from './myplugin.js'
app.use(myplugin, { info: '配置信息' })

可以看到,让插件生效的语法为app.use,这样就可以跟Vue结合到一起,所以插件就可以独立出去,成为第三方模块。