Vue进阶属性
directives //(指令)对普通 DOM 元素进行底层操作,就会用到自定义指令
mixins // (混入)是一种分发 Vue 组件中可复用功能的非常灵活的方式。
extends //(继承)都是扩展vue组件时使用,和mixins类似,只不过使用方法不同
provide //(提供)可以把data数据提供给所有人与inject 相关使用
inject //(注入)把可以用的东西放在自己身上与provide相关
directive指令--减少DOM操作的重复
指令的作用:主要用于DOM操作
- Vue实例 / 组件用于数据绑定、事件监听、DOM更新
- Vue指令主要目的就是原生DOM操作
- 减少重复
- 如果某个DOM操作你经常使用,就可以封装为指令。比如事件绑定经常做,那就写成
v-on
指令。 - 如果某个DOM操作比较复杂,也可以封装为指令
1. Vue自带的指令:v-if ,v-for ,v-html等等
2. 自己造一个指令(官方文档)
两种声明方式
方法1:声明一个全局指令
Vue.directive('x', directiveOptions)
//就可以在任何组件里用v-x了
在main.js里写
//完整语法
Vue.directives("x",directiveOptions)
//举例
//在main.js里写
Vue.directive('x',{
inserted: function (el) { //这里的el是一个全局元素,只要把v-x放在某个元素上,这个el就是那个元素
el.addEventListener('click', ()=>{console.log('x')})
}
})
//在全局任何一个dom标签都可以使用v-x调用
方法2:声明一个局部指令
在options里写,只能被那个Vue实例/组件使用
new Vue({
...,
directives:{
"x":directiveOptions
}
})
或者在script里面的export default里写
关于directiveOptions
directiveOptions
是个对象,里面有五个函数属性,这五个函数是钩子函数,它们规定了指令什么时候生效
- 函数属性
- bind(el, info, vnode, oldVnode)
类似created,只调用一次,指令第一次绑定到元素时调用。 - inserted(参数同上)
类似mounted,被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。 - update(参数同上) //少用
类似 updated - componentUpdated(参数同上) //少用
用得不多,见文档 - unbind(参数同上)
类似destroyed,当元素要消亡时调用。
- 属性参数:
el:绑定指令的那个元素
info:是个对象,用户调用指令时,与指令相关的数据,比如监听什么事件,监听到事件执行什么函数
vnode:虚拟节点
oldVnode:之前的虚拟节点
举例:写一个v-on的简单的v-on2
一开始我们还是使用Vue自带的v-on: ,通过点击button是可以打印出hi的
import Vue from "vue/dist/vue.js"; // 故意使用完整版
Vue.config.productionTip = false;
new Vue({
template: `
<button v-on:click="hi">点我</button>
`,
methods: {
hi() {
console.log("hi");
}
}
}).$mount("#app");
但是现在不能用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");
至于info里面的arg,value是怎么来的呢,是通过console.log打印来的,就可以知道如果我们v-on2后面接的什么事件
- arg是对应用户传来的方法
- value是把用户传的方法还是什么的东西,给你算好,我们拿他的value就可以了
如果不用v-on2,我们还有方法
点击h1 h3 打印出x
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
就可以控制消亡了。
Child1组件写好了,那剩下的四个组件呢?
关于每个(子)组件的共同操作,可以用mixins
新建src/mixins/log.js,把公共的东西(Child1.vue的options)
剪切到log.js
里面,在导出。
但是注意,以前的name:Child1
被写死了,可是其他的组件不能用这个名字啊,所以把name:undefined;
之后每个组件在自己里面写name:Childx
,就会智能覆盖undefined
,父级data
会覆盖mixins
里的data
const log = {
data(){
return {
name:undefined, //这里的name可以写undefined,在下面判断用户要传入一个name
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`)
}
}
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>
</div>
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
export default {
name: "App",
data() {
return {
Child1Visible: true,
Child2Visible: true,
};
},
components: {
Child1,
Child2,
}
};
</script>
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
;
1,我们先把换肤的按钮弄出来以组件的形式,然后导入每一个Child的子组件
//这个文件叫ChangeThemeButton.vue
<template>
<div>
<button>换肤</button>
</div>
</template>
2,先导入子组件,以Child1为例
<template>
<div>Child 1
<change-theme-button/>
<!-- 看清楚组件的大小写,这里变了 -->
<!-- 意思是vue支持用'-'来替换字母大写 -->
</div>
</template>
<script>
import ChangeThemeButton from "./ChangeThemeButton.vue";
export default {
components: {
ChangeThemeButton
}
};
</script>
3,我们(ChangeThemeButton.vue)怎么获得祖先(App.vue)的themeName呢?
在提供数据(祖先App.vue)的地方写入provide
在需要数据(ChangeThemeButton.vue)的地方注入inject我们需要的数据
App.js文件
ChangeThemeButton.vue文件
(子孙代)换肤按钮组件把themeName注入自己就行
但是祖先传过来的themeName到我们这只是我们复制的一个字符串。我们改了我们的字符串themeName并不会改祖先的themeName。
所以祖先得写一个可以修改祖先自己的themeName的函数提供给换肤按钮组件,这样换肤按钮组件才可以真的改themeName
所以提供一个methods方法,来改变主题,
下面是字体大小变化(直接给出代码)
App.vue
<template>
<div :class="`app theme-${themeName} fontsize-${fontSizeName}`">
<!-- 上面这一句是js语句:class="`app theme-${themeName}`" -->
<!-- 两个双引号是class的值,用XML语法括起来,不是JS引号,不能删-->
<!-- 第一个class是app,第二个class是theme-${themeName} -->
</div>
</template>
<script>
import Child1 from "./components/Child1.vue";
import Child2 from "./components/Child2.vue";
export default {
name: "App",
provide() {
return {
themeName: this.themeName,
changeTheme: this.changeTheme,
changeFontSize: this.changeFontSize,
};
},
data() {
return {
themeName: "blue", // 'red'
fontSize: "normal", // 'big' | 'small'
};
},
methods: {
changeTheme() {
if (this.themeName === "blue") {
this.themeName = "red";
} else {
this.themeName = "blue";
}
},
changeFontSize(name) {
if (["normal", "big", "small"].indexOf(name) >= 0) {
this.fontSizeName = name;
}
},
},
components: {
Child1,
Child2,
},
};
</script>
<style>
.app.theme-blue button {
/* 这里解释一下 .app.theme-blue和.app .theme-blue */
/* 上面是有空格的,有空格和每空格的区别 */
/* 没加空格意思是.app和.theme-blue这两个class同时存在 */
/* 而加了空格就是.app里面的.theme-blue */
background: blue;
color: white;
}
.app.theme-blue {
color: darkblue;
}
.app.theme-red button {
background: red;
color: white;
}
.app.theme-red {
color: darkred;
}
.app button {
font-size: inherit;
}
.app.fontSize-normal {
font-size: 16px;
}
.app.fontSize-big {
font-size: 20px;
}
.app.fontSize-small {
font-size: 12px;
}
</style>
ChangeThemeButton.vue文件
<template>
<div>
<button @click="z">换肤</button>
<button @click="changeFontSize('small')">小字</button> //传入一个值给父组件的函数调用
<button @click="changeFontSize('big')">大字</button>
<button @click="changeFontSize('normal')">正常字</button>
</div>
</template>
<script>
export default {
inject: ["themeName", "changeTheme", "changeFontSize"],
methods: {
z() {
this.changeTheme();
},
},
};
</script>
Child1.vue文件
<template>
<div>
Child 1
<change-theme-button />
</div>
</template>
<script>
import ChangeThemeButton from "./ChangeThemeButton.vue";
export default {
components: {
ChangeThemeButton,
},
};
</script>
总结
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要修改子代的,因此emit是在父代里修改子代然后传回到子代(子代先给父代信息),provide | inject是在子代修改父代(父代给子代信息)
- 注意普通传值传的就是值,如果原来的data里相应属性是个对象,那么传的就是这个对象本身,即你在子代修改父代是会真的改变的provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。传入父代的method其实也是传的函数本身
this问题
只有data和method里面属性this才有意义,因为他们将挂靠在vm实例上,每个组件的实例不同,当provide传递函数时,子代接受函数挂靠在子代实例上
inject: ["themeName", "changeTheme", "changeFontSize"],
methods:{
z(){
this.changeTheme()
}
}
};