什么是Mixin(混入)
Mixin是一种思想,用来实现代码高度可复用性,可以针对属性复制实现代码复用的想法进行一个扩展,就是混入(mixin)。混入并不是复制一个完整的对象,而是从多个对象中复制出任意的成员并将这些成员组合成一个新的对象。
1 const obj1 = {a:1,b:2,c:3,d:4,e:5};
2 const obj2 = {f:6,g:7,h:8,i:9,a:10};
3 let obj3 = {};
4 Object.assign(obj3,obj1,obj2);
5 console.log(obj3);
6 // 输出结果 {"a":10,"b":2,"c":3,"d":4,"e":5,"f":6,"g":7,"h":8,"i":9};
上面是一个对象混入,把两个对象,合并成一个对象,如果有相同属性存在则后者会覆盖原有的属性值,如果存在方法名相同同样会覆盖,因为在对象内部是以key来区分的,并且每个key是唯一的。
为什么要使用混入
Mixin的优点
混入可以减少程序中重复的功能,增强函数的复用性。当一个应用程序可能需要在各种对象中共享行为时,我们可以通过Mixin中维持这种共享功能并专注于程序中真正不同的功能,轻松避免任何重复,使项目结构更加清晰更加易于维护。
Mixin的缺点
Mixin是一种很灵活的代码复用方式,但把功能属性和方法导入,如果文件过多,会导致属性方法来源方面的不确定性,在大型系统中需要对项目文件的划分以及整体的掌控。
Vue Mixins 应用
官方解释
混入 (mixins) 是一种分发 Vue 组件中可复用功能的非常灵活的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
mixins.js
export default { data: function () { return { message: 'hello', foo: 'abc' } } }
index.js
new Vue{ mixins: [mixin], data: function () { return { message: 'goodbye', bar: 'def' } }, created: function () { console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" } } }
在实际项目中可以根据业务领域把相同的业务场景按文件进行划分,比如好多页面都需要插入/删除/更改,这些方法都是相同的,可以单独放在一个js文件里面去统一的管理。
再举一个例子,比如有很多页面里面都有表单,然而这些页面内的表单都需要方法去校验,每个页面都写一次的话,会导致大量的重复代码的出现,而且不易于管理,维护,我们可以把所有的表单的校验方法放在一个js文件里面导出去,哪些组件或者页面需要进行格式校验,使用mixin混入这些方法。即使是以后需要添加或者更改校验方法就直接更改这个js文件就好了。
唯一一点很重要的就是,混入的方法会覆盖掉之前页面内部的同名属性或方法。
官方文档:Vue Mixins 文档
小程序Mixins应用
小程序的Page并没有给出混入的API以及解方案,如果想要实现混入,需要自己封装Mixins方法,来实现对象方法以及属性的混入,达到代码的复用的目的。
Mixins实现
在小程序初始化使用App去创建每个页面,通过Page可以获取到创建页面时所需要的属性和方法,如果把Page看做是一个函数,然而在函数里面写的方法或者属性,其实都放在了一个对象里面,data也好方法也好其实都是这个对象里面唯一的属性和方法,为页面提供消费。
export let CreatePage = (obj) => { let {mixins} = obj; Reflect.deleteProperty(obj, 'mixins'); obj.data = obj.data || {}; mixins.forEach((el) => { merge(el,obj) }) return obj; } let merge = (el,obj) => { let elKeys = Object.keys(el); el.data = el.data || {}; elKeys.forEach((e) => { e === "data" && Object.assign(obj.data,el.data); (e !== "data") && (obj[e] = el[e]); }) }
上面代码中创建了一个名为CreatePage的函数,函数接收了一个对象作为参数,然而这个对象就是我们在使用Page的时候传入供页面消费的对象,在对象内部添加了一个属性Mixins,这个属性是一个数组,里面的每一项都是需要混入到里面的属性和方法。遍历Mixins数组,获取到其中的每一项,使用merge函数把里面的每一项都进行合并。由于data是一个对象,直接和并可能会导致只有合并后的data数据,合并之前的数据丢失,所以做了判读如果遇到data就单独去处理data的合并。其他的属性或者方法直接合并到obj里面,最后把obj返回出去。
应用
index.js
// 引入mixin import {CreatePage} from "../../utils/mixin.js"; // 引入静态数据 mixin1 import mixin1 from "../../mixins/public/mixin1.js"; Page(CreatePage({ mixins:[mixin1], data:{ one:"我是index里面的数据" }, a(){ console.log("我是a函数",this.data.two) }, onLoad(){ this.a(); this.b(); } }))
mixin1.js
module.exports = { data:{ two:"我是Mixin里面的数据" }, b(){ console.log("我是b函数",this.data.one) } }
通过这种方法可以把项目中业务重复的方法以及静态数据放到mixins文件中,统一管理统一维护。一旦项目需求发生变化更改一个地方就可以了,没有必要在成百行代码中找到某行代码,无疑是增加大量的查找以及修改的作业。
mixins 改进
let native = Page; Page = (obj) => { let {mixins = []} = obj; let merge = new Merge(); Reflect.deleteProperty(obj, "mixins"); let pageData = mixins.length<=0?obj:merge.start(obj,...mixins); native(pageData); } class Merge { constructor(){} start(rootObj,...objs){ let root = {}; objs.forEach((el) => { root = Merge.recursive(rootObj,el); }) return root; } static recursive = (rootObj,obj) => { for(let attr in obj){ if(rootObj[attr] === undefined){ rootObj[attr] = obj[attr]; } else if(Merge.isObject(obj[attr])){ Merge.recursive(rootObj[attr],obj[attr]) } else{ rootObj[attr] = obj[attr]; } } return rootObj; } static isObject = (obj) => { return Object.prototype.toString.call(obj).includes("Object"); } }
在第一种方法上做了进一步的改进,上面的代码中首先存储了一份Page函数,然后获取到Page里面的参数进行深度拷贝处理,深度拷贝完毕之后,把拷贝后的对象return出去。把存储的Page执行一下,最后把合并后的data传进去就好了。
改进应用
index.js
// 引入静态数据 mixin1 import mixin1 from "../../mixins/public/mixin1.js"; Page({ mixins:[mixin1], data:{ one:"我是index里面的数据" }, a(){ console.log("我是a函数",this.data.two) }, onLoad(){ this.a(); this.b(); } })
mixin1.js
module.exports = { data:{ two:"我是Mixin里面的数据" }, b(){ console.log("我是b函数",this.data.one) } }
改进之后无需再使用createPag函数,之前使用Page的时候,使用方法是一样的,只是里面添加一个`mixins`属性而已,用来混入那些公用的属性或者方法。
优点
代码改进后不仅仅只有Page可以使用呢mixins,`Component`也可以通过上面的方法,重新改写一下`Component`在组件中使用mixins。
总结
随然mixins模式,使代码变得复用性很强,但是任何事物都有两面性,如果只是为了应用而应用的话很可能会适得其反,没有达到理想的效果。个人认为,强大的文档有助于将与混入函数来源有关的困惑减至最低,但对于每一种模式,如果在实现期间多加注意,多做考虑,一定会应用的很顺利的。