directives指令--减少DOM操作的重复
指令的作用:主要用于DOM操作
- Vue实例/组件用于数据绑定、事件监听、DOM更新
- Vue指令主要目的就是原生DOM操作
- 减少重复
- 如果某个DOM操作你经常使用,就可以封装为指令。比如事件绑定经常做,那就写成
v-on指令。 - 如果某个DOM操作比较复杂,也可以封装为指令
1. Vue自带的指令
2. 自己造一个指令(官方文档)
两种声明方式
方法一:声明一个全局指令
在main.js里
Vue.directives("x",directiveOptions)
方法二:声明一个局部指令
在options里写,只能被那个Vue实例/组件使用
new Vue({
...,
directives:{
"x":directiveOptions
}
})关于directiveOptions
directiveOptions是个对象,里面有五个函数属性,这五个函数是钩子函数,它们规定了指令什么时候生效
created,只调用一次,指令第一次绑定到元素时调用。2. inserted(参数同上)
mounted,被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。3. update(参数同上)
类似
updated4. 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
- 在这五个组件
created、destroyed时,打出提示,并报出存活时间 - 请问你怎么做? 给每个组件添加data和钩子,共五次 或者使用mixins减少重复
先写组件Child1
- 当child1组件出生就打印出“child1出生了”
- 那就需要个data里面name:child1
- created函数
- 还需要个时间。那就data里面time为出生时间
- 在出生时记录时间(
new Date),给time赋值
- 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>- 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
- 新建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- 每个组件如何使用(就是复制就是复制)?先引入,再放到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的操作
- 在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里相应属性是个对象,那么传的就是这个对象本身,即你在子代修改父代是会真的改变的
provide和inject绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。传入父代的method其实也是传的函数本身
this问题
只有data和method里面属性this才有意义,因为他们将挂靠在vm实例上,每个组件的实例不同,当provide传递函数时,子代接受函数挂靠在子代实例上
inject: ["themeName", "changeTheme", "changeFontSize"], methods:{ z(){ this.changeTheme() } }};