Vue中的 mixins 和 provide/inject详解
作者:努力的小朱同学
一、mixins
1、简介
mixins
又称 混入,是指将一些可复用的代码(JS、生命周期钩子函数等等)抽离出来,定义成mixins
模块,然后混入到多个组件中,从而实现组件间的逻辑代码共享,减少重复代码。当组件使用mixins
模块时,mixins
模块内部的代码将会被“混合”进组件的代码,代码“混合”逻辑与Vue.extend()
相同,具体逻辑下面有讲解。
2、基础使用
mixins
模块在组件内是通过与data
、mounted
、methods
等钩子函数同级的mixins
钩子调用的,该钩子的值是一个数组,里面包含要混入当前组件的mixins
模块,这些模块的混入顺序按照mixins
钩子数组的排列顺序执行,而且mixins
模块内如果包含生命周期钩子函数,则模块内的钩子函数执行顺序先于组件本身的钩子函数。
案例代码:
// 定义一个mixin模块 mixin.js export default { created: function () { console.log('这里是mixin1模块的created') } } // 在组件实例中引入并使用定义的mixin模块 // 引入mixin模块 import mixin from "../mixins/mixin"; export default { created: function () { console.log('这里是组件本身的created') }, // 使用mixin模块 mixins: [mixin] }
执行结果:
3、选项合并
当组件与引入的mixins
模块含有同名选项时,这些钩子将依照下面的规则进行合并( Vue.extend()
的合并逻辑相同):
① mixins
模块中的data
数据对象,将与组件的data
数据进行递归合并,如果存在同名数据,则取组件内的数据值。
当组件引入多个mixins
模块时,将按照mixins
钩子数组的排列顺序进行合并,如果mixins
模块之间存在同名数据,则取排序最后的那个mixins
模块的数据值,当然,如果该数据在组件内也存在,则还是取组件内的数据值。
案例代码:
// 定义一个mixin1.js模块 export default { data() { return { a: 1, // 第一个混入模块中的变量a b: 11 // 第一个混入模块中的变量b } }, } // 定义一个mixin2.js模块 export default { data() { return { a: 2, // 第二个混入模块中的变量a b: 22 // 第二个混入模块中的变量b } }, } // 在组件实例中引入并使用定义的mixin模块 // 引入两个mixin模块 import mixin1 from "../mixins/mixin1"; import mixin2 from "../mixins/mixin2"; export default { data() { return { b: 0, // 组件内的变量b }; }, mounted() { // 组件内不存在这个变量 两个混入模块存在同名变量 最终值取决于模块引用顺序 console.log("经过合并后变量a的值是-----", this.a); // 组件内存在这个变量 最终值取组件内的值 console.log("经过合并后变量b的值是-----", this.b); }, // 使用两个mixin模块 注意先后顺序 决定同名变量的最终取值 mixins: [mixin1, mixin2], }
执行结果:
② mixins
模块中的钩子函数,将与组件内的同名钩子函数合并成一个数组,依次执行,且mixins
模块中的钩子函数在组件同名钩子函数之前调用执行。不同名的钩子函数,依旧按照钩子函数的先后顺序执行。
当组件引入多个mixins
模块时,如果mixins
模块之间存在同名钩子函数,则会按照mixins
钩子数组的排列顺序合并成一个数组,排列顺序与执行顺序相同,排序靠前的mixins
模块中的同名钩子函数先执行,排序靠后的后执行,最终才会执行组件本身的同名钩子函数。
案例代码:
// 定义一个mixin1.js模块 export default { created: function () { console.log('这里是mixin1模块的created') }, } // 定义一个mixin2.js模块 export default { created: function () { console.log('这里是mixin2模块的created') }, mounted: function () { console.log('这里是mixin2模块的mounted') }, } // 在组件实例中引入并使用定义的mixin模块 // 引入两个mixin模块 import mixin1 from "../mixins/mixin1"; import mixin2 from "../mixins/mixin2"; export default { created() { console.log("这里是组件本身的created"); }, // 使用两个mixin模块 注意先后顺序 决定同名钩子函数的执行顺序 mixins: [mixin1, mixin2], }
执行结果:
③ mixins
模块中的methods
、components
等值为对象的选项,将会与组件内部的对应选项合并为一个对象,当对象中的键名发生冲突时,则取组件内的键名对应的值。
当组件引入多个mixins
模块时,如果mixins
模块之间的选项存在同名冲突时,则会按照mixins
钩子数组的排列顺序进行覆盖,后面的mixins会覆盖前面的mixins,键名对应的值将取排在最后的mixins
模块中对应的值,当然,如果该键名在组件内也存在,则最终还是取组件内的对应的值。
案例代码:
// 定义一个mixin1.js模块 export default { methods: { test() { console.log('这里是mixin1模块methods中的test函数') }, test1() { console.log('这里是mixin1模块methods中的test1函数') } }, } // 定义一个mixin2.js模块 export default { methods: { // 如果mixin模块之间存在键名冲突 则以组件中mixin数组的引用顺序为准 // 取排序最后的键名对应的值 test1() { console.log('这里是mixin2模块methods中的test1函数') } }, } // 在组件实例中引入并使用定义的mixin模块 // 引入两个mixin模块 import mixin1 from "../mixins/mixin1"; import mixin2 from "../mixins/mixin2"; export default { mounted() { this.test(); this.test1(); }, // 使用两个mixin模块 注意先后顺序 决定键名冲突的最终结果 mixins: [mixin1, mixin2], methods: { // 如果mixin模块与组件本身键名冲突 则以组件为最终结果 test() { console.log("这里是组件本身methods中的test函数"); }, }, }
执行结果:
总结:
当一个组件使用了多个mixins
时,它们的顺序很重要。因为当mixins
之间的选项存在冲突时,后面的mixins
会覆盖前面的mixins
。而且同名钩子函数的执行顺序也取决于多个mixins
的顺序。
当组件与mixins
的选项存在冲突时,一切以组件为准。
在使用多个mixins
时,记得注意命名冲突问题。
4、全局混入
上面我们举的例子都是在组件中依次引入mixins
模块,如果我们想要在多个组件中,甚至是所有组件中都引入某个mixins
模块,如果在每个组件中都引入一次,就太过繁琐。此时我们可以使用全局混入特性。
全局混入是指将声明的mixins
模块,在main.js
文件中的全局Vue实例创建之前,通过Vue.mixin()
方法,挂载到Vue上。其作用相当于全局引入了mixins
模块,会影响到每一个单独创建的Vue页面实例和组件,因此请慎用该特性!!!
如果全局混入的mixins
模块中包含生命周期钩子函数,那么该钩子函数将会根据当前页面做包含的Vue实例数量来决定执行的次数,main.js文件的中的 全局Vue实例也算。
案例代码:
// 定义一个allmixin.js 模块 export default { created: function () { console.log('这里是全局mixin模块的created') }, methods: { test() { console.log('这里是全局mixin模块methods中的test函数') } }, } // 在main.js中引入并进行全局混入 import Vue from 'vue' import App from './App.vue' import allMixin from './mixins/allMixin' // 一定要在 new Vue 之前 否则不起作用 Vue.mixin(allMixin) // 创建全局Vue实例 new Vue({ render: h => h(App), }).$mount('#app') // 此时的页面结构 // mian.js的new Vue -> APP.vue -> test.vue
执行结果:
5、自定义选项
我们该可以结合this.$options
自定义选项来使用全局混入,只有使用自定义选项的组件才会触发相关逻辑,从而局限mixins
模块中部分代码的作用范围:
案例代码:
// 定义一个allmixin.js 模块 export default { created: function () { // 自定义选项 并接收传递的值 const myOptionValue = this.$options.myOption // 输出传递的值 if (myOptionValue) { console.log('-*------', myOptionValue) } }, } // 在main.js中引入并进行全局混入 import Vue from 'vue' import App from './App.vue' import allMixin from './mixins/allMixin' // 一定要在 new Vue 之前 否则不起作用 Vue.mixin(allMixin) // 创建全局Vue实例 new Vue({ render: h => h(App), }).$mount('#app') // 在组件使用全局混入中自定义的选项 export default { data() { return {} }, // 使用自定义选项 myOption: "这是我向自定义选项传递的字符串", }
执行结果:
mixins
模块中的自定义选项在合并时,使用的是默认策略,即简单的覆盖已有的值。当然我们也可以通过Vue.config.optionMergeStrategies
来自定义合并逻辑:
// 自定义合并逻辑 Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) { // 返回合并后的值 } // 或者 // 采用现有逻辑 与methods相同 Vue.config.optionMergeStrategies.myOption = Vue.config.optionMergeStrategies.methods
二、provide/inject
1、简介
provide/inject
是Vue在2.2.0版本新增的特性,该特性可以实现祖先组件向其后代组件跨层级传递数据,无论两者中间相隔多少组件层级,相对于传统的props
传递方式,减少了传递数据的繁琐操作,提升了代码的可读性和可维护性。该特性与React 框架的上下文特性很相似。
但是过多的使用provide/inject
特性,会增加组件之间的耦合性,降低组件的可复用性,因此使用该特性时要谨慎,不可滥用。而且provide
和inject
的绑定并不是响应式的,传递的数据并不会自动响应数据变化,如果想要响应数据变化,请借助data
或computed
。
2、provide
provide
选项需要在祖先组件中使用,作用是向后代组件传递数据,其选项值为一个对象或一个返回值为对象的函数,对象的属性即为要向其后代组件传递的数据。传递的数据可以是任意类型的数据,如基本类型、对象、函数等等。
provide
选项中支持使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol
和 Reflect.ownKeys
的环境下可工作
案例代码:
// provide 的选项值为一个对象 export default { data() { return {} }, provide: { a: "这是祖先组件向后代组件传递的字符串数据", b: { c: "这是祖先组件向后代组件传递的对象数据", }, f: function () { console.log("这是祖先组件向后代组件传递的函数数据"); }, }, } // provide 的选项值为一个返回值为对象的函数 export default { data() { return {} }, provide() { return { a: "这是祖先组件向后代组件传递的字符串数据", b: { c: "这是祖先组件向后代组件传递的对象数据", }, f: function () { console.log("这是祖先组件向后代组件传递的函数数据"); }, }; }, }
3、inject
inject
选项是在后代组件中使用,作用是接收祖先组件传递的数据,其选项值为一个字符串数组(推荐)或一个对象。更推荐使用字符串数组的形式,其中数组元素对应的是provide
对象中的key
,通过this.数组字符串元素
的形式来访问祖先组件传递的对应数据;如果使用对象形式,则需要通过键值对来接收数据,键名表示当前组件内的访问名称,value
为字符串,对应的是provide
对象中的key
,通过this.键名
的形式来访问祖先组件传递的对应数据。
案例代码:
// inject 的选项值为一个字符串数组(推荐) export default { data() { return {} }, inject: ["a", "b", "f"], } // inject 的选项值为一个对象(不推荐) export default { data() { return {} }, inject: { a: "a", b: "b", f: "f", }, } // 在后代组件中通过inject接收传递的数据之后 调用传递的数据 mounted() { console.log("inject接收的祖先组件传递过来的字符串数据-----", this.a); console.log("inject接收的祖先组件传递过来的对象数据-----", this.b); console.log("inject接收的祖先组件传递过来的函数数据-----", this.f); },
执行结果:
4、进阶知识
① 在Vue的2.2.1版本之后,后代组件中通过inject
接收传递的数据,会在props
和data
初始化之前得到,因此我们可以使用inject
中的数据给props
和data
中的数据设置默认值。
export default { inject: ['foo'], props: { a: { default() { return this.foo } } }, data() { return { b: this.foo } }, }
② 在Vue的2.5.0 版本之后,我们可以给选项值为对象形式的inject
中的数据设置默认值,使其在祖先组件中变成可选项,即在不传递数据时,后代组件依旧能正常工作。from
属性设置数据源,default
属性设置默认值。
与props
设置默认值类似,如果直接将非原始值(复杂数据类型)作为默认值,那么它将成为所有子组件实例之间共享的引用,相互之间会产生影响。因此我们需要对非原始值(复杂数据类型)使用一个工厂方法,以便于每次使用默认值时都获得一个新的副本,而不是共享同一个引用。
export default { inject: { // 当组件内名称与祖先组件的key相同时,可省略form属性 foo: { default: 'foo' }, // 当组件内名称与祖先组件的key不同时,需要通过form属性指定对应的数据 bar: { from: 'barFather', default: 'bar' }, // 对复杂数据类型使用一个工厂方法 arr: { from: 'arr', default: () => [1, 2, 3] }, }, data() { return {} }, }
三、参考资料
到此这篇关于Vue中的 mixins 和 provide/inject详解的文章就介绍到这了,更多相关Vue mixins 和 provide/inject内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!