前景:
在原生小程序的开发过程中,发现有很多页面使用了几乎完全一样的逻辑,例如:我们会遇到有部分功能需要登录之后才能访问,这个登录的逻辑就可以用封装成公共的逻辑,在需要的地方直接引用就可以了。
但是由于小程序官方并没有提供 Mixins 这种代码复用机制,所以只能用复制粘贴的方式去复用代码。随着功能越来越复杂,这样显得很不优雅,于是就想着怎么在小程序里面实现 Mixins。
什么是 Mixins
vue的官方定义:
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被"混合"进入该组件本身的选项。
vue中使用mixins
javascript// 定义一个混入对象 var myMixin = { created: function () { this.hello() }, methods: { hello: function () { console.log('hello from mixin!') } } } // 定义一个使用混入对象的组件 var Component = Vue.extend({ mixins: [myMixin] }) var component = new Component() // => "hello from mixin!"
Mixins 的机制
1、当组件和混入对象含有同名选项时,比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先
javascriptvar mixin = { data: function () { return { message: 'hello', foo: 'abc' } } } new Vue({ mixins: [mixin], data: function () { return { message: 'goodbye', bar: 'def' } }, created: function () { console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" } } })
2、同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
javascriptvar mixin = { created: function () { console.log('混入对象的钩子被调用') } } new Vue({ mixins: [mixin], created: function () { console.log('组件钩子被调用') } }) // => "混入对象的钩子被调用" // => "组件钩子被调用"
3、值为对象的选项,例如
methods
、components
和directives
,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
javascriptvar mixin = { methods: { foo: function () { console.log('foo') }, conflicting: function () { console.log('from mixin') } } } var vm = new Vue({ mixins: [mixin], methods: { bar: function () { console.log('bar') }, conflicting: function () { console.log('from self') } } }) vm.foo() // => "foo" vm.bar() // => "bar" vm.conflicting() // => "from self"
以上是vue中Mixins 的机制
在小程序中,这套机制会和 vue的有一些区别。
在小程序中,自定义的方法是直接定义在 Page 的属性当中的,既不属于生命周期类型属性,也不属于对象类型属性。
小程序的 Mixins 运行机制多加一条:
小程序中的自定义方法,优先级为 Page > Mixins;
即 Page 中的自定义方法会覆盖 Mixins 当中的。
小程序实现方法
在小程序中,每个页面都由 Page(options) 函数定义,而 Mixins 则作用于这个函数当中的 options 对象。
因此小程序实现 Mixins 的思路就有了------劫持并改写 Page 函数,最后再重新把它释放出来。
Mixins结构如下:
新建一个wx-mixins.js文件
javascript
import { mergeOptions } from './merge-options';
// 保存原生的 Page 函数
const originPage = Page;
// 重新定义 Page
Page = (options) => {
// 处理 mixins
const mixins = options.mixins;
// mixins 必须为数组
if (Array.isArray(mixins)) {
delete options.mixins;
// mixins 注入并执行相应逻辑
options = merge(mixins, options);
}
// 释放原生 Page 函数
originPage(options)
}
新建一个merge-options.js文件
javascript
/**
* 扩展 Page 的 mixins 选项
* mixins 同名字段的覆盖与合并规则
* 同名生命周期函数都会被调用,混入对象的生命周期函数在页面生命周期函数之前被调用
* data 对象在内部会进行浅合并,并在命名发生冲突时以页面数据优先
* 混入对象的属性或方法命名冲突时,页面定义的属性或方法优先
*/
// Page 生命周期函数列表
const pageHooks = [
"onLoad",
"onShow",
"onReady",
"onHide",
"onUnload",
"onPullDownRefresh",
"onReachBottom",
"onShareAppMessage",
"onPageScroll",
"onResize",
"onTabItemTap"
]
const hasOwnProperty = Object.prototype.hasOwnProperty;
const toString = Object.prototype.toString
function toRawType(value) {
return toString.call(value).slice(8, -1);
}
function isPlainObject(value) {
return toRawType(value) === "Object";
}
function isFunction(value) {
return toRawType(value) === "Function";
}
function hasOwn(obj, key) {
return hasOwnProperty.call(obj, key);
}
function mergeOptions(mixins, options, hooks) {
mixins.forEach(mixin => {
if (!isPlainObject(mixin)) {
throw new Error("typeof mixin must be plain object")
}
// 混入对象中嵌套混入对象,递归合并
if (mixin.mixins) {
mixin = mergeOptions(mixin.mixins, mixin, hooks)
}
// 处理混入对象中每一个值, 可能是生命周期函数、可能是 data 或 方法
Object.getOwnPropertyNames(mixin).forEach(function (key) {
// 暂存页面中初始的值
const originValue = options[key]
// 暂存混入对象的值
const mixinValue = mixin[key]
// 处理混入对象的生命周期函数
if (pageHooks.includes(key)) {
if (!isFunction(mixinValue)) {
throw new Error(`typeof ${key} must be function`)
}
// 重写页面的生命周期函数
options[key] = function () {
let res;
// 先执行混入对象的生命周期函数
res = mixinValue.apply(this, arguments)
// 后执行页面中的生命周期函数
if (originValue) {
res = originValue.apply(this, arguments)
}
return res
}
}
// 混入对象的值是对象
else if (isPlainObject(mixinValue)) {
options[key] = {
...mixinValue,
...originValue
}
}
// 混入对象的属性或方法
else {
// 页面中不存在同名的属性或方法时,才使用混入对象的属性或方法
if (!hasOwn(options, key)) {
options[key] = mixinValue
}
}
})
})
return options
}
module.exports = {
mergeOptions: mergeOptions
}
Mixins 使用
1.在小程序的 app.js 里引入 mixins.js
javascript
import './mixins/wx-mixins';
2.撰写一个 myMixin.js
javascript
// 全局app实例
const app = getApp();
export default {
data: {},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function () {
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
}
}
3.在 page/index/index.js 中使用
javascript
Page({
mixins: [require('../../myMixin.js')]
})