Vue构造选项--options--(三)--directives、mixins、extends、provide/inject

279 阅读6分钟
const vm = new Vue(
options
)

继续学习Vue构造选项options的进阶属性。

今天学习的进阶属性属于资源类和组合类

1、directives-指令

声明一个全局指令

全局指令就是在所有组件里都能用的指令。

Vue.directive( id, [definition] )
Vue.directive("x", directiveOptions)
  • {string} id
  • {Function | Object} [definition]

代码示例

main.js

Vue.directive("x" , {//声明一个指令叫做x
  inserted: function (el) {
  //当元素被插入到页面中之后
  //这里的元素指的是你的指令v-x放到的那个元素
  //全局指令v-x可以在任何组件的任何标签/元素上使用
    el.addEventListener("click",()=>{console.log("x")})
    //我们就监听它的click事件,addEventListener的意思添加事件监听
  },
})

练习 codesandbox.io/s/recursing…

声明一个局部指令

在组件HelloWorld.vue或App.vue里面

<script>
export default {
  name: "HelloWorld",
  directives: {
    x: {
      inserted(el) {
        el.addEventListener("click", () => {
          console.log("x");
        });
      }
    }
  },
  props: {
    msg: String
  }
};
</script>

或者new Vue({})里面

new Vue({
    ...,
    directives:{
        "x": directiveOptions
    }
})

局部指令v-x只能用在该实例中或该组件中。

directiveOptions里有哪些属性(钩子函数)?

  • bind (可接受的参数有 el,info,vnode,oldVnode) - 类似created
  • inserted(参数同上) -类似mounted
  • update(参数同上) -类似update
  • componentUpdated(参数同上) -用的不多
  • unbind(参数同上) -类似destroyed

自制v-on2指令,模仿v-on

new Vue({
  directives: {
    on2: {
    //生成一个名叫on2的指令,on2是简化版的v-on
      // bind 可以改为 inserted
      bind(el, info) {
      //el:指令所绑定的元素,可以用来直接操作 DOM。
      //info(binding):一个对象{},包含以下name,value,arg,modifiers等property
      
        console.log("info")//打出info,看看里面的内容,找到arg和value就是我们需要监听和调用的
        console.log("info.arg")//验证arg
        console.log("info.value")//验证value
        el.addEventListener(info.arg, info.value);
        //对元素添加事件监听,监听'info.arg'事件,并调用'info.value',value就是下面methods中的hi函数
        // Vue 自带的 v-on 并不是这样实现的,它更复杂,用了事件委托
      },
      unbind(el, info) {
        el.removeEventListener(info.arg, info.value);
      }//当元素从页面消失的时候,移除事件监听。
    }
  },
  template: `
    <button v-on2:click="hi">点我</button>
  `,
  methods: {
    hi() {
      console.log("hi");
    }
  }
}).$mount("#app");

指令的作用-减少重复的DOM操作。

vue指令主要用于DOM操作:vue把DOM操作封装成一个vue指令,只传一个函数给这个指令,我们就不用接触DOM操作了。(DOM操作就是:用DOM的API来操作页面。)

而vue实例/组件用于数据绑定、事件监听、DOM更新(不是用DOM的API更新,而是直接通过监听器更新)。

vue指令使得我们不用再在实例里面进行DOM操作,所有DOM操作都封装到了vue指令里面。

new Vue({
  directives: {xxx:{封装指令}}}).$mount("#app");

如果某个DOM操作你经常使用,可以封装为指令。 如果某个DOM操作比较复杂,也可以封装为指令。

详见 cn.vuejs.org/v2/api/#Vue…

2、mixins-混入-把豆子(代码)倒进来

directives的作用是减少DOM操作的重复。

mixins的作用:

  • 减少data、methods、钩子等options的重复。
  • 将data()、created()等options导入xxx.vue文件,并自动与xxx.vue文件里面的data()、created()等options智能合并。

示例代码log.js -> Child1.vue-Child5.vue -> App.vue -> main.js

codesandbox.io/s/cocky-bre…

log.js

const log = {//声明一个叫做log的对象。
  data() {
    return {
      name: undefined,
      time: undefined//时间记录
    };
  },
  created() {//当组件出生时
    if (!this.name) {//也可写为'this.name === undefined'
    //如果this.name不存在
      throw new Error("need name");
    //就报错"need name"
    }
    this.time = new Date();//记录created()出生时刻
    console.log(`${this.name}出生了`);
  },
  beforeDestroy() {//当组件消亡前
    const now = new Date();//记录beforeDestroy()死亡时刻

    console.log(`${this.name}死亡了,共生存了 ${now - this.time} ms`);
  }
};

export default log;//导出log对象

知识点回顾:JavaScript Date 对象

var myDate=new Date()//Date 对象会自动把当前日期和时间保存为其初始值。

详见 www.w3school.com.cn/jsref/jsref…

Child.vue

<template>
  <div>Child1</div>
</template>

<script>
import log from "../mixins/log.js";//从上一层文件minxins里面的log.js里面接入log对象。
export default {
  data() {
    return {
      name: "Child1"
    };//给Child1一个name
  },
  created() {
    console.log("Child 1 的 created");
  },
  mixins: [log]//把log对象里面的[data(),created(),beforeDestroy()]一个options组复制到这里
};
</script>

App.vue

知识点回顾:v-if条件渲染元素

根据表达式的值的 truthiness 来有条件地渲染元素。

<template>
  <div id="app">
    <Child1 v-if="child1Visible"/>
    <!-- 当"child1Visible = true"时,让child1显示 -->
    <button @click="child1Visible = false">x1</button>
    <!-- 当点击button时,让child1隐藏 -->
    ...
    <Child5 v-if="child5Visible"/>
    <button @click="child5Visible = false">x5</button>
  </div>
</template>

main.js

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

new Vue({
  render: h => h(App)//渲染组件App
}).$mount("#app");

可以在main.js里面写Vue.mixin,但不推荐使用。

3、extends-继承

回顾1、directives和2、mixins的知识

  • directives-vue指令封装DOM操作(v-xxx封装),减少DOM操作的重复。
  • mixins-用xxx.js封装data、methods、钩子等options,再用
<script>
  export default {
    mixins: [log]
}
</script>

导入xxx.vue组件。减少了data、methods、钩子等options的重复。

  • extends是比mixins更抽象一点的封装。如果要写多次mixins,可以用extends来封装,实际工作用到很少。

  • 可以使用Vue.extend/options.extends

示例代码log.js -> MyVue.js -> Child1.vue-Child5.vue -> App.vue -> main.js

log.js与2、mixins中示例一样

const log = {//声明一个叫做log的对象。
  data() {},
  created() {},
  beforeDestroy() {};
export default log;//导出log对象

MyVue.js

import Vue from "vue";
import log from "./mixins/log.js";
const MyVue = Vue.extend({//使用"Vue.extend"得到一个新的vue类,这个类可以去new它
    mixins: [log]
});

export default MyVue

Child.vue

<template>
  <div>Child1</div>
</template>

<script>
import MyVue from "../MyVue.js";
export default {
  data() {
    return {
      name: "Child1"
    };//给Child1一个name
  },
  extends:MyVue//继承MyVue里面的data(),created(),beforeDestroy()
};
</script>

App.vue和main.js里面的代码与2、mixins中示例一样。

4、provide-提供;inject-注入

  • 父组件App.vue通过provide(){return{}}提供data(){}methods:{}的属性给其他组件修改和调用。

  • 子组件/孙组件xxx.vue通过inject:["","",""]注入,来修改和调用这些由父组件提供的data(){}methods:{}的属性。

示例代码ChangeThemeButton.vue -> Child1-Child5.vue -> App.vue ->main.js codesandbox.io/s/friendly-…

ChangeThemeButton.vue

<template>
  <div>
    <button @click="changeTheme">换肤</button>
    <button @click="changeFontSize('big')">大字</button>
    <!-- 当点击这个"大字"按钮的时候,通过inject和provide,调用函数changeFontSize,size的值为"big";
    当size变化,this.fontSizeName = size = big ,
    App.vue里面的<div :class="`app theme-${themeName} fontSize-${fontSizeName}`">就会变化为fontSize-big,
    就会影响App.vue里面CSS部分的内容。 -->
    
    <button @click="changeFontSize('small')">小字</button>
    <button @click="changeFontSize('normal')">正常字</button>
  </div>
</template>
<script>
export default {
  inject: ["themeName", "changeTheme", "changeFontSize"]
  //这些注入的属性可以用this. 调用
};
</script>

Child1.vue

<template>
  <div>Child 1
    <change-theme-button/>
    <!-- 插入ChangeThemeButton.vue里面的XML -->
    <!-- 也可以用<ChangeThemeButton/>,改成如上的小写形式也行,用中号线连接 -->
  </div>
</template>

<script>
import ChangeThemeButton from "./ChangeThemeButton.vue";//引入ChangeThemeButton.vue的js代码
export default {
  components: {
    ChangeThemeButton
  }
};
</script>

App.vue

//XML部分
<template>
  <div :class="`app theme-${themeName} fontSize-${fontSizeName}`">
  <!-- 双引号里面`app theme-${themeName} fontSize-${fontSizeName}`这一部分 是class的值,此双引号不是js的双引号,是XML的双引号。 -->
  <!-- 反引号``代表的是js的引号,表明反引号里面的部分是js字符串 -->
  <!-- js字符串里面的内容,'app'表示这个div的第一个class是app -->
  <!-- 'theme-${themeName}'表示 div的第二个class是'theme-${themeName}',这个"themeName"存的是按钮组的颜色 -->
  <!-- 'fontSize-${fontSizeName}'表示 div的第三个class是'fontSize-${fontSizeName}'这个"fontSizeName"存的是按钮组的字体大小 -->
    <Child1/>
    <button>x</button>
    ···
  </div>
</template>

//JS部分
<script>
import Child1 from "./components/Child1.vue";
······
import Child5 from "./components/Child5.vue";
export default {
  name: "App",
  provide() {
  //在提供data的地方,写一个'provide(){}'函数
  //提供给其他组件比如ChangeThemeButton.vue来绑定事件修改或调用。
  
    return {
      themeName: this.themeName,
      //把data里面的themeName提供给别人修改。
      
      fontSizeNmae: this.fontSizeName,
      //把data里面的fontSizeName提供给别人修改。
      
      changeTheme: this.changeTheme, 
      // 把methods: {}里面的changeTheme函数提供给别人调用。
      
      changeFontSize: this.changeFontSize
      // 把methods: {}里面的changeFontSize函数提供给别人调用。
    };
  },
  data() {
    return {
      themeName: "blue", // 'red'
      fontSizeName: "normal" // 'big' | 'small'
    };
  },
  methods: {
    changeTheme() {
      if (this.themeName === "blue") {
        this.themeName = "red";
      } else {
        this.themeName = "blue";
      }
    },
    changeFontSize(size) {
      if (["normal", "big", "small"].indexOf(size) === -1) {
        throw new Error(`wront size: ${size}`);
      }//如果size的下标为-1,则报错`wront size: ${size}`
      this.fontSizeName = size;
      //如果size的下标不为-1,即为0或1或2,则size的值必然是["normal", "big", "small"]中的一个,则令this.fontSizeName = size
    }
  },
  components: {
    Child1,
    ···
  }
};
</script>


//CSS部分
<style>
.app.theme-blue button {
/* 上述语句表示:如果.app这个类同时也属于.theme-blue这个类,那么button就用以下样式*/
/* 注意,这里的'.app'后面是没有空格的。如果".app"".theme-blue"之间有空格,就表示.app这个类里面,包含.theme-blue这个类。*/
  background: blue;
  color: white;
}

.app.fontSize-big {
  font-size: 20px;
}
/* 如果一个元素div同时满足".app"".fontSize-big"这两个选择器,那么就让它的font-size: 20px */

.app button {
  font-size: inherit;
}
/* 按钮继承当前字体的大小 */
</style>

回顾知识点:CSS里面的fontSize-

provide/inject 总结

作用:大范围的data和method等的共用。

注意:不能只provide themeName不传 changeTheme,因为themeName的值是被复制给provide的字符串,不能达到效果。

那传引用可以吗?可以,但是不推荐,因为容易失控。

总结

1、directives指令

  • 全局用 Vue.directive("x",{...})
  • 局部用 options.directives
  • 作用是减少DOM操作相关重复代码

2、mixins混入

  • 全局用 Vue.mixin({...})
  • 局部用 options.mixins:[mixin1,mixin2]
  • 作用是减少options里的重复

3、extends继承/扩展

  • 全局用 Vue.extend({...})
  • 局部用 options.extends:{...}
  • 作用和mixins差不多,extends是比mixins更抽象一点的封装。如果要写多次mixins,可以用extends来封装,实际工作用到很少。

4、provide/inject 提供和注入

  • 父组件通过provide(){return{}}提供data(){}methods:{}的属性给其他组件修改和调用。
  • 子组件/孙组件xxx.vue通过inject:["","",""]注入,来修改和调用这些由父组件提供的data(){}methods:{}的属性。
  • 作用的大范围、隔N代共享信息。