【Vue全解7】options之组合(mixin、extends、provide/inject)

376 阅读4分钟

目录

  • 系列文章
  • mixin混入
  • extends继承
  • provide提供/inject注入

一、系列文章

【Vue 全解 0】Vue 实例

【Vue 全解 1】构造选项 options 之 data

【Vue 全解 2】Vue 模板语法摘要

【Vue 全解 3】Vue 的 data 代理和数据响应式

【Vue 全解 4】options 之生命周期钩子(created、mounted、updated、destroyed)

【Vue 全解 5】options 之 DOM(el、template、render)

【Vue 全解 6】options 之资源(directive、filter、components)和修饰符

【Vue 全解 7】options 之组合(mixin、extends、provide/inject)

【Vue 全解 8】Vue 表单输入绑定 v-model

二、mixin混入

mixins 选项接收一个混入对象的数组。这些混入对象可以像正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中,使用的是和 Vue.extend() 一样的选项合并逻辑。也就是说,如果你的混入包含一个 created 钩子,而创建组件本身也有一个,那么两个函数都会被调用。

Mixin 钩子按照传入顺序依次调用,并在调用组件自身的钩子之前被调用。

1、作用

  • 混入就是复制
  • 减少data、methods、钩子的重复

2、场景描述

假设我们需要在每个组件上添加name和time。在created、destroyed时,打出提示,并给出存活时间。

  • 一共有五个组件,请问怎么做?
  1. 做法1:给每个组件添加data和钩子,重复5次
  2. 做法2:使用mixin减少重复。
  • 做法1:重复5次

子组件1:child1.vue


<template>
	<div>child1</div>
</template>
<script>
	export default{
		data(){
			return {
				name:'child1',
				time_birth:undefined,
				time_dead:undefined
			}
		},
		created(){
			this.time_birth=new Date();
			console.log(this.name+'出生了');
		},
		beforeDestroy(){
			this.time_dead=new Date();
			console.log(`${this.name}死了,共存活了${this.time_dead-this.time_birth}ms`);
		}
	}
</script>

子组件2:child2.vue

<template>
	<div>child2</div>
</template>
<script>
	export default{
		data(){
			return {
				name:'child2',
				time_birth:undefined,
				time_dead:undefined
			}
		},
		created(){
			this.time_birth=new Date();
			console.log(this.name+'出生了');
		},
		beforeDestroy(){
			this.time_dead=new Date();
			console.log(`${this.name}死了,共存活了${this.time_dead-this.time_birth}ms`);
		}
	}
</script>

子组件3:child3.vue

<template>
	<div>child3</div>
</template>
<script>
	export default{
		data(){
			return {
				name:'child3',
				time_birth:undefined,
				time_dead:undefined
			}
		},
		created(){
			this.time_birth=new Date();
			console.log(this.name+'出生了');
		},
		beforeDestroy(){
			this.time_dead=new Date();
			console.log(`${this.name}死了,共存活了${this.time_dead-this.time_birth}ms`);
		}
	}
</script>

子组件4:child4.vue

<template>
	<div>child4</div>
</template>
<script>
	export default{
		data(){
			return {
				name:'child4',
				time_birth:undefined,
				time_dead:undefined
			}
		},
		created(){
			this.time_birth=new Date();
			console.log(this.name+'出生了');
		},
		beforeDestroy(){
			this.time_dead=new Date();
			console.log(`${this.name}死了,共存活了${this.time_dead-this.time_birth}ms`);
		}
	}
</script>

子组件5:child5.vue

<template>
	<div>child5</div>
</template>
<script>
	export default{
		data(){
			return {
				name:'child5',
				time_birth:undefined,
				time_dead:undefined
			}
		},
		created(){
			this.time_birth=new Date();
			console.log(this.name+'出生了');
		},
		beforeDestroy(){
			this.time_dead=new Date();
			console.log(`${this.name}死了,共存活了${this.time_dead-this.time_birth}ms`);
		}
	}
</script>

主组件:App.vue

<template>
	<div id="app">
		<child1 v-if='child1visible'/>
		<button v-on:click='!child1visible'>toggle1</button>
		<hr>
		<child2 v-if='child2visible'/>
		<button v-on:click='!child2visible'>toggle2</button>
		<hr>
		<child3 v-if='child3visible'/>
		<button v-on:click='!child3visible'>toggle3</button>
		<hr>
		<child4 v-if='child4visible'/>
		<button v-on:click='!child4visible'>toggle4</button>
		<hr>
		<child5 v-if='child5visible'/>
		<button v-on:click='!child5visible'>toggle5</button>
	</div>
</template>
<script>
import child1 from './child1.vue';
import child2 from './child2.vue';
import child3 from './child3.vue';
import child4 from './child4.vue';
import child5 from './child5.vue';
export default {
	name:'App',
	data(){
		return {
			child1visible:true,
			child2visible:true,
			child3visible:true,
			child4visible:true,
			child5visible:true,
		}
	},
	compoents:{
		child1,
		child2,
		child3,
		child4,
		child5
	}
}
</script>

做法1小结:代码重复太多,基本上每个组件都在复刻第一个组件的样式,这样下来,代码的维护性是十分低的。万一有一天要改需求了怎么办?又倒回去重新修改5次吗?5次并不是真正的5次,万一是10次,100次呢?很显然,这种做法并不可取。接下来来看另外一种做法。


  • 做法2:使用mixin减少重复

1、先将每个子组件的输出部分(相似的代码)提出来放在一个公共的文件中,我们将它放在Mixins文件夹中吧,取名为public.js。注意要将其导出

const public = {
	data(){
			return {
				/* 每个组件的名字不同,用另外的方法赋值。即每个组件自己带上自己的名字即可。 */
				name:undefined,
				time_birth:undefined,
				time_dead:undefined
			}
		},
		created(){
			if(!this.name){
				throw new Error('need name');
			}
			this.time_birth=new Date();
			console.log(this.name+'出生了');
		},
		beforeDestroy(){
			this.time_dead=new Date();
			console.log(`${this.name}死了,共存活了${this.time_dead-this.time_birth}ms`);
		}
}
export default public;

2、子组件1:child1.vue -- 其他4个组件也是类似的操作。这里就不展示了,最主要的是突出重复5次的操作是真的很傻,而且代码维护也很难。

<template>
	<div>child1</div>
</template>
<script>
	/* 首先导入公共部分的js内容 */
	import public from './Mixins/public.js';
	export default{
		data(){
			return {
				/* 其他组件就写自己组件的名字即可 */
				name:'child1',
			}
		}
		/* 使用导入的public模块 */
		mixins:['public'],
	}
</script>

主组件:App.vue -- 主组件的内容全都是必要的,没有办法再精简了。

<template>
	<div id="app">
		<child1 v-if='child1visible'/>
		<button v-on:click='!child1visible'>toggle1</button>
		<hr>
		<child2 v-if='child2visible'/>
		<button v-on:click='!child2visible'>toggle2</button>
		<hr>
		<child3 v-if='child3visible'/>
		<button v-on:click='!child3visible'>toggle3</button>
		<hr>
		<child4 v-if='child4visible'/>
		<button v-on:click='!child4visible'>toggle4</button>
		<hr>
		<child5 v-if='child5visible'/>
		<button v-on:click='!child5visible'>toggle5</button>
	</div>
</template>
<script>
import child1 from './child1.vue';
import child2 from './child2.vue';
import child3 from './child3.vue';
import child4 from './child4.vue';
import child5 from './child5.vue';
export default {
	name:'App',
	data(){
		return {
			child1visible:true,
			child2visible:true,
			child3visible:true,
			child4visible:true,
			child5visible:true,
		}
	},
	compoents:{
		child1,
		child2,
		child3,
		child4,
		child5
	}
}
</script>

mixin小结:

  • 选项会智能合并--就是当公共部分有created函数同时主组件也有created函数时的情况。
  • Vue.mixin--用到时看文档,不推荐使用

三、extend继承

允许声明扩展另一个组件 (可以是一个简单的选项对象或构造函数),而无需使用 Vue.extend。这主要是为了便于扩展单文件组件。

这和 mixins 类似。

  • 借用上面的例子

创建一个公共的 js 文件,用于组件使用它来进行继承

import Vue from 'vue';	//导入vue
const Myvue = Vue.extend({
	data(){
			return {
				/* 每个组件的名字不同,用另外的方法赋值。即每个组件自己带上自己的名字即可。 */
				name:undefined,
				time_birth:undefined,
				time_dead:undefined
			}
		},
		created(){
			if(!this.name){
				throw new Error('need name');
			}
			this.time_birth=new Date();
			console.log(this.name+'出生了');
		},
		beforeDestroy(){
			this.time_dead=new Date();
			console.log(`${this.name}死了,共存活了${this.time_dead-this.time_birth}ms`);
		}
});
export default Myvue;

子组件:其他的操作都是类似的,主组件的内容也没有改变

<template>
	<div>child1</div>
</template>
<script>
	/* 首先导入公共部分的js内容 */
	import Myvue from './Myvue.js';
	export default{
		extends: Myvue,
		data(){
			return {
				/* 其他组件就写自己组件的名字即可 */
				name:'child1',
			}
		}		
	}
</script>

extend继承小结:extends是比mixins更抽象一点的封装。如果你嫌写mixins烦,可以考虑使用extend。但是实际上用mixins就够了。


四、provide/inject

祖先栽树(provide),后人乘凉(inject)

需求 1 :一键换肤功能,默认为蓝色,可以切换为红色。文字大小,默认正常,可以改大或者改小。

  • 代码演示

主组件

<template>
	<div v-bind:class="`app theme-${themeName} fontSize-${fontSizeName}`">
		<child1/>
		<button>toggle1</button>
		<hr>
		<child2/>
		<button>toggle2</button>
		<hr>
		<child3/>
		<button >toggle3</button>
		<hr>
		<child4/>
		<button>toggle4</button>
		<hr>
		<child5/>
		<button>toggle5</button>
	</div>
</template>
<script>
import child1 from './child1.vue';
import child2 from './child2.vue';
import child3 from './child3.vue';
import child4 from './child4.vue';
import child5 from './child5.vue';
export default {
	name:'App',
	provide(){
		return {
			themeName:this.themeName,
			changeTheme:this.changeTheme,
			changeFontSize:this.changeFontSize
		}
	},
	data(){
		return {
			themeName:'blue',	//'red'
			fontSizeName:'normal'	//'big' | 'small'
		}
	},
	methods:{
		changeTheme(){
			if(this.themeName==='blue'){
				this.themeName='red';
			}
			else{
				this.themeName='blue';
			}
		},
		changeFontSize(name){
			if(name in ['normal','big','small']){
				this.fontSizeName=name;
			}
		}
	}
	compoents:{
		child1,
		child2,
		child3,
		child4,
		child5
	}
}
</script>
<style>
	.app.theme-blue button{
		background:blue;
		color:white;
	}
	.app.theme-blue{
		color:darkblue;
	}
	.app.theme-red button{
		background:red;
		color:white;
	}
	.app.theme-red{
		color:darkred;
	}
	.app.fontSize-normal {
		font-size: 16px;
	}
	.app.fontSize-big {
		font-size: 24px;
	}
	.app.fontSize-small{
		font-size: 8px;
	}
	.app button {
		/* 让按钮继承字体的大小 */
		font-size:inherit;
	}
</style>

子组件:切换主题的按钮--文件名为:ChangeThemeButton.vue

<template>
	<div>
		<button v-on:click='x'>当前主题色:{{themeName}}换肤</button>
		<hr>
		<button v-on:click='changeFontSize("big")'>大字</button>
		<hr>
		<button v-on:click='changeFontSize("small")'>小字</button>
		<hr>
		<button v-on:click='changeFontSize("normal")'>正常字</button>
	</div>
</template>
<script>
	export default {
		inject:['themeName','changeTheme','changeFontSize'],//注意themeName这里只是拿到一个字符串的复制品,并不能修改到主组件的内容。需要传送一个主组件能修改的函数changeTheme。
		methods:{
			x(){
				this.changeTheme;
			}
		}
	}
</script>

子组件:其他5个都是这样类似

<template>
	<div>
	child1
	/* <change-theme-button> 也可以*/
	<ChangeThemeButton/>
	</div>
</template>
<script>
	import ChangeThemeButton from './ChangeThemeButton.vue';
	export default {
		compoents:{ChangeThemeButton}
	}
</script>

provide/inject总结:

  • 适用于大范围的data和methods共用
  • 注意:不能只传themeName不传changeTheme,因为App.vue里面的themeName的值是被复制给provide的。
  • 传引用可以(就是传一个对象,改对象里面的数据)。但是不推荐,容易失控。