VUE(六)-options(三)

226 阅读6分钟

directives指令--减少DOM操作的重复

指令的作用:主要用于DOM操作

  1. Vue实例/组件用于数据绑定、事件监听、DOM更新
  2. Vue指令主要目的就是原生DOM操作
  3. 减少重复
  •  如果某个DOM操作你经常使用,就可以封装为指令。比如事件绑定经常做,那就写成v-on指令。
  • 如果某个DOM操作比较复杂,也可以封装为指令

1. Vue自带的指令

2. 自己造一个指令(官方文档)

两种声明方式

方法一:声明一个全局指令

在main.js里 Vue.directives("x",directiveOptions)

方法二:声明一个局部指令

在options里写,只能被那个Vue实例/组件使用

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

关于directiveOptions

directiveOptions是个对象,里面有五个函数属性,这五个函数是钩子函数,它们规定了指令什么时候生效

1. bind(el, info, vnode, oldVnode)
类似created,只调用一次,指令第一次绑定到元素时调用。
2. inserted(参数同上)
类似mounted,被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
3. update(参数同上)
类似 updated
4. componentUpdated(参数同上)
用得不多,见文档
5. unbind(参数同上)
类似destroyed,当元素要消亡时调用。


参数:

el:绑定指令的那个元素

info:是个对象,用户调用指令时,与指令相关的数据,比如监听什么事件,监听到事件执行什么函数

vnode:虚拟节点

oldVnode:之前的虚拟节点

例子:写一个v-on的简单的v-on2

new Vue({
  directives: {
    on2: {
      //当元素出现在页面时,会调用bind函数,我把bind函数写成添加事件监听
      //bind和inserted都行
      bind(el, info) {
        //console.log(info); //打印出info,看看我们需要他的哪些信息
        el.addEventListener(info.arg, info.value);
      },
      //添加了事件监听,那就想办法在一定的时机删掉,不然越累积越多
      //当元素要消亡时,会调用unbind函数,我把unbind函数写成删除事件监听
      unbind(el, info) {
        el.removeEventListener(info.arg, info.value);
      }
    }
  },
  template: `
    <button v-on2:click="f1">点我</button>  //button使用了我们写的指令
  `,
  methods: {
    f1() {
      console.log("Hi");
    }
  }
}).$mount("#app");

上面这个例子,v-on2指令,指令操作是绑定事件所以可能会和v-on区分不开,其实自定义指令的指令操作可以不是绑定事件,只是操作dom即可比如加个active类,v-on就干不了,需要在生命周期里面干这种事情

Mixins混入,就是复制---减少options构造选项的重复

一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

就是把共同的options构造选项复制到需要用的Vue实例/组件里

作用

  • 减少重复
  • directives的作用是减少DOM操作的重复
  • mixins的作用是减少data、methods、钩子的重复,等构造选项的重复
  • options里的构造选项都可以先放到一个js文件,之后哪个实例/组件需要就导入并且用mixins使用就行。

智能合并

写在了共同东西里的东西被组件引用了之后,组件还可以覆盖他们,Vue会智能合并,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。

同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

发送冲突组件优先

全局的mixins:不推荐

在main.js里写

Vue.mixins({公用的的options选项})

这样所有的组件都会用这个,所以不推荐。

例子

场景描述

  • 非完整版Vue,App.vue引用了五个组件
  • 假设我们需要在每个组件上添加name和time
  • 在这五个组件createddestroyed时,打出提示,并报出存活时间
  • 请问你怎么做? 给每个组件添加data和钩子,共五次 或者使用mixins减少重复

先写组件Child1

  1. 当child1组件出生就打印出“child1出生了”
  • 那就需要个data里面name:child1
  • created函数
  • 还需要个时间。那就data里面time为出生时间
  • 在出生时记录时间(new Date),给time赋值
  1. child1组件死亡了就打印出“child1死亡了,共生存了多少ms”
  • beforeDestroy
  • 获得当前时间
  • 那么存活时间就是当前时间-出生时间
//Child.vue
<template>
<div>Child1</div>
</template>

<script>
export default {
  data(){
    return {
      name:"Child1",
      time:undefined  //time即将用来表示出生时间
      }
  },
  //当这个组件出生了就执行created函数。
  created(){
    //把出生时间记录下来
    this.time=new Date()
    console.log(`${this.name}出生了`)
  },
  //当组件死掉之前,执行这个函数。注意不是死掉之后,死掉之后数据都没了!
  beforeDestroy(){
    //把死亡时间记录下来
    const now = new Date()
    console.log(`${this.name}死亡了,共生存了${now-this.time}ms`)
  }
}
</script>
  1. Child1组件出生是当然的,所以created函数自动执行了。那Child1组件怎么消亡呢?
  • Child1组件是被App.vue所使用的。所以用App.vue来控制这个组件消亡
  • 消亡就是把这个组件不被App.vue所用,就是把组件从DOM树里弄消失
  • 在data里Child1Visible:true(默认不消亡)
  • 使用这个组件的时候判断一下,如果Child1Visible是false就不出现在DOM树里,就是消亡了。所以点击按钮的时候让Child1Visible变成false就可以控制消亡了。
//App.vue
<template>
  <div id="app">
    <Child1 v-if="Child1Visible"/>  //用Child1Visible判断是否从DOM树里移走也就是死亡
    <button @click="Child1Visible=false">x</button>  //点击按钮就会让Child1Visible变成false
    <Child2/>
    <button>x</button>
    <Child3/>
    <button>x</button>
    <Child4/>
    <button>x</button>
    <Child5/>
    <button>x</button>
  </div>
</template>

<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
import Child3 from "./components/Child3.vue";
import Child4 from "./components/Child4.vue";
import Child5 from "./components/Child5.vue";
export default {
  name: "App",
  data() {
    return {
      Child1Visible:true   //添加Child1Visible,默认不消亡
    };
  },
  components: {
    Child1,
    Child2,
    Child3,
    Child4,
    Child5
  }
};
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Child1组件写好了,那剩下的四个组件呢?

关于每个(子)组件的共同操作,可以用mixins

  1. 新建src/mixins/log.js,把公共的东西(Child1.vue的options)剪切到log.js里面,在导出。
  • 但是注意,以前的name:Child1被写死了,可是其他的组件不能用这个名字啊,所以把name:undefined;之后每个组件在自己里面写name:Childx,就会智能覆盖undefined,父级data会覆盖mixins里的data
const log = {options}
export default log
  1. 每个组件如何使用(就是复制就是复制)?先引入,再放到mixins里(就是复制就是复制)。别忘了写name:Childx
//Child1.vue
<template>
<div>Child1</div>
</template>

<script>
import log from "../mixins/log.js";
export default {
  data(){
    return {
      name:"Child1"                 //智能覆盖log中的name
    }
  },
    mixins:[log]  //把公共的复制到我身体里了
}
</script>

关于(父组件)App.vue的操作

  1. 在App.vue里还是得把每个子组件的操作再做一遍的
<template>
  <div id="app">
    <Child1 v-if="Child1Visible"/>
    <button @click="Child1Visible=false">x</button>
    <Child2 v-if="Child2Visible"/>
    <button @click="Child2Visible=false">x</button>
    <Child3 v-if="Child3Visible"/>
    <button @click="Child3Visible=false">x</button>
    <Child4 v-if="Child4Visible"/>
    <button @click="Child4Visible=false">x</button>
    <Child5 v-if="Child5Visible"/>
    <button @click="Child5Visible=false">x</button>
  </div>
</template>

<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
import Child3 from "./components/Child3.vue";
import Child4 from "./components/Child4.vue";
import Child5 from "./components/Child5.vue";
export default {
  name: "App",
  data() {
    return {
      Child1Visible: true,
      Child2Visible: true,
      Child3Visible: true,
      Child4Visible: true,
      Child5Visible: true,
    };
  },
  components: {
    Child1,
    Child2,
    Child3,
    Child4,
    Child5
  }
};
</script>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Extends 继承、扩展

  • extends是比mixins更抽象一点的封装
  • 如果你的mixins里面有十个那就很麻烦,可以考虑extends一次
  • 不过实际工作中用得很少
  • 你可以使用Vue.extend或options.extends得到一个新的类,我们可以new MyVue
const MyVue = Vue.extend({
data(){ 
    return {name: “' ,time:undefined} 
},
created(){
if( !this . name){
    console. error('no name!')
}
this. time = new
Date( )
},
beforeDestroy(){
const duration = (new Date()) - this. time 
console . log( ${ this . name}存活时间$ { duration}^ )
})
export default MyVue

然后我们就可以使用new MyVue(options)

Vue.extend()使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。

extends,在没有调用 `Vue.extend` 时候继承和mixin一样

provide | inject提供和注入

  • 祖先提供东西,后代注入东西
  • 作用是大范围、隔N代共享信息(data、methods等)

例子:一键换肤(代码,代码)

  • 点击换肤按钮会切换class:${themeName}(blue|red)来切换css从而改变颜色
  • 每个子组件都要有换肤按钮
  • 那就把换肤按钮也写成一个组件,其他组件导入使用就行了
  • 可是themeName是App.vue的data,换肤按钮组件怎么拿到祖先的东西?
  • 那就让(祖先)App.vue提供themeName;

(子孙代)换肤按钮组件把themeName注入自己就行


  • 但是祖先传过来的themeName到我们这只是我们复制的一个字符串。我们改了我们的字符串themeName并不会改祖先的themeName。所以祖先得写一个可以修改祖先自己的themeName的函数提供给换肤按钮组件,这样换肤按钮组件才可以真的改themeName


<template>  <div>    <button @click="changeTheme">换肤</button>    <button @click="changeFontSize('big')">大字</button>    <button @click="changeFontSize('small')">小字</button>    <button @click="changeFontSize('normal')">正常字</button>  </div></template><script>export default {  inject: ["themeName", "changeTheme", "changeFontSize"]};console.log(this)</script>

 provide() {    return {      themeName: this.themeName,      fontSizeNmae: this.fontSizeName,      changeTheme: this.changeTheme, // 如果不生效,刷新一下 codesandbox      changeFontSize: this.changeFontSize    };  },

例子:选择字体大小

  • 点击字体按钮会切换class:${fontSizeName}(normal|big|small)来切换css从而改变字体大小
  • 写css:当.app.fontsize-normal ;当.app.fontsize-big ; 当.app.fontsize-small
  • App.vue得提供可以修改自己的fontSizeName的函数给按钮组件

总结

directives指令

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

mixins混入

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

extends继承/扩展

  • 全局用Vue.extend({.})
  • 局部用options.extends: {...}
  • 作用跟mixins差不多,只是形式不同

provide | inject提供和注入

  • 祖先提供东西,后代注入东西 *作用是大范围、隔N代共享信息
  • 与emit不同,provide | inject 要修改的是父代的,而emit要修改子代的,因此emit是在父代里修改子代然后传回到子代(子代先给父代信息),provide | inject是在子代修改父代(父代给子代信息)
  • 注意普通传值传的就是值,如果原来的data里相应属性是个对象,那么传的就是这个对象本身,即你在子代修改父代是会真的改变的provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。传入父代的method其实也是传的函数本身

this问题

只有data和method里面属性this才有意义,因为他们将挂靠在vm实例上,每个组件的实例不同,当provide传递函数时,子代接受函数挂靠在子代实例上

  inject: ["themeName", "changeTheme", "changeFontSize"],  methods:{    z(){      this.changeTheme()    }  }};