教你在小程序中实现一个混入功能

706 阅读4分钟

小程序中使用混入功能

在小程序的开发过程中,肯定会有一些公共逻辑避免不了重复书写,使用过vue的同学都知道,混入能很好地帮我们处理一些公共逻辑。那么如何在小程序中自己实现一个混入功能呢?(小程序官方已经提供了混入功能behaviors)。 文章最下方有quick-start和github地址

实现原理: 小程序的页面都是通过Page()函数注册的,想要加入混入功能,可以通过修改Page()函数实现

1、首先我们先明确混入的使用方法,参考vue混入的使用方式

//test.js  

const test={
    onLoad:function(){
        console.log('混入load')
      
    },
    data:{
        testMixins:'我被混入到页面中了1',
        name:'test1'
    },
    oper(){
        // console.log('混入了自定义函数')
    },
    initData(){
        console.log('initData函数被调用')
    },
    onShareAppMessage() {
        var that = this;
        var title = '积分商城';
        return {
          title: title,
          path: '/pages/integralShop/integralShop' 
        };
        
    },
}

const  test2={
    data:{
        testMixins:'我被混入到页面中了2',
        name:'test2'
    },
    onLoad:function(){
        console.log('混入load2')
    }
}


module.exports={
    test,
    test2
}


//index.js
import  {test,test2} from "../../mixins/test"
Page({
    mixins:[test,test2],
	data:{
		
	},
	onLoad(){
		
	}
})

混入策略如下

(1)合并声明周期函数,混入逻辑在前,页面在后

mixins:[test1,test2] 如果都有onload生命周期 ,调用顺序test1.onload=>test2.onload=>page.onload

(2)合并data数据,如果存在同名属性,后加载的混入优先级高,page中优先级最高

mixins:[test1,test2] 如果data中都有message属性,test2中的message 会覆盖test1中的,页面中的会覆盖test2中的

(3)合并自定义方法,如果存在同名方法,后加载的混入优先级高,page中优先级最高

mixins:[test1,test2] 如果data中都有getMessage属性,test2中的getMessage 会覆盖getMessage中的,页面中的会覆盖test2中的

2、基于上述的使用方式,我们来开始实现混入

(1)首先需要定义好可以进行混入的生命周期,和页面属性

/**
 * 页面生命周期
 */
const  pageLifeTimes = {
    'onLoad' : [],
    'onShow' : [],
    'onReady' : [],
    'onHide' : [],
    'onUnload' : [],
    'onPullDownRefresh' : [],
    'onReachBottom' : [],
    'onShareAppMessage' : [],
    'onPageScroll' : [],
    'onResize' : [],
    'onTabItemTap' : [],
}

const pageProperties = {
    'data':{}
}
    

(2)混入之后,我们期望的是在index.js 调用this.onLoad(),会先执行混入中的onLoad,然后执行index.js定义的onLoad,这里我们就是用到一种设计模式 "装饰者模式",可以在不修改原函数的情况下,在执行函数之前或之后加上自己的行为。

/**
 * 装饰器函数
 * @params {function} fn 原函数
 * @params {desc}   装饰函数的数组
 */
function decorator(fn,desc){
    return function(){

        //页面生命周期函数存在,先执行混入生命周期函数,后执行页面生命周期函数
         let context= desc.reduce((ctx,item)=>{
            return item.apply(this,arguments)
        },{})

        if(fn&&typeof fn =='function'){
            return fn.apply(this,arguments)
        }
		
		//这里返回一个执行内容,是为了兼容onShareAppMessage
        return context
    }
}

(3)替换page函数

/**
 *  初始化混入
*/
function initMixins(config={}){
    //先存储原page函数
    const  oriPage = Page;

    //定义新的page函数
    Page = options =>{
        
        const mixins=Array.isArray(options.mixins)?options.mixins:[];
        //全局混入
        if(config.globalMixins){
            mixins.unshift(config.globalMixins)
        }
        //获取传入的混入对象数组,进行选项的合并
        if(mixins.length>0){
            options= mixinOptions(options,mixins)
        }
        oriPage(options)
    }
}

执行选项合并的具体实现

/**
 * @desc    混入
 * @param {object} options page函数原有options
 * @param {array} mixins   要混入的options
 */
function mixinOptions(options,mixins){
    //因为pageLifeTimes是纯粹的数据,所以直接使用序列化和反序列化来进行一个深拷贝
    let pageLifeTimesCopy=JSON.parse(JSON.stringify(pageLifeTimes));
    mixins.forEach((mixin) =>{
        //混入选项是否是对象
        if(!isObject(mixin)){
            throw new TypeError('mixin is not a object')
        }
        for (let [key ,vlaue] of  Object.entries(mixin)){
            //生命周期函数合并
            if(Object.keys(pageLifeTimesCopy).includes(key)){
                pageLifeTimesCopy[key].push(mixin[key])
                continue
            }

            //页面属性合并
            if(Object.keys(pageProperties).includes(key)){
                pageProperties[key] = {...pageProperties[key],...mixin[key]}
                continue
            }

            //自定义方法,页面中的优先级高
            if(!options[key]){
                options[key] =mixin[key]
            } 
        }
    })
    //混入生命周期
    for(let key  in pageLifeTimesCopy){
        let mixins=pageLifeTimesCopy[key];
        if(mixins.length>0){
            options[key] = decorator(options[key],pageLifeTimesCopy[key])
        }  
    }
    //混入页面属性
    for(let key  in pageProperties){
         options[key] = {...pageProperties[key],...options[key] }
        
    }
    return options
}

(4) 在app.js中进行初始化

只需要在app.js引入minMixins 文件,然后调用初始化混入函数,初始化是可传入一个config参数,目前只有一个属性, 如果不需要全局混入,则可省略

属性名称类型描述
globalMixinsobject全局混入的对象
//app.js
import initMixins from  "./utils/minMixins"
import globalMixins from "./mixins/globalMixins"
initMixins({globalMixins})
App({
  onLaunch() {

  }
})

项目地址

quickstart 小程序代码片段,可直接导入微信开发者工具

github