差异抹平
模板语法/基础组件
在编译时进行模板语法/基础组件的抹平,列出了各大平台支持的所有组件,为每个组件单独提供了一个配置文件,在配置文件中写明了各平台对该组件的的支持和属性支持,如果该平台不支持写的某些属性或者直接不支持该组件,会在编译时抛错
属性条件编译
可以通过属性条件编译解决各平台属性不兼容问题,比如支付宝小程序navigator组件没有target属性,可以使用条件编译@target:wx="xx",编译时如果是支付宝平台会把这个属性去掉
原理是编译时设置好了编译目标平台的全局变量,在编译时,会在所有属性对象上加上平台描述属性,如果是编译到支付宝平台,则只保留平台属性为空或者支付宝的
json配置
比如微信支持disableScroll,支付宝不支持,而且微信支付宝设置标题样式的属性也不一样,对这种,我们是在代码中写了两份单独的配置,通过预设置的编译目标全局变量判断使用哪一份,然后通过webpack的treeshaking可以把用不到的一份摇掉
api调用
这一层是编译时+运行时处理的。
编译时:
将所有wx.api的调用转为mpx.api,从而将所有原生接口的调用都经过mpx的代理
运行时:
我们在项目初始化时会调用mpx封装的apiProxy包,包内按wx的接口调用逻辑重新封装了其他平台的对应接口,如果调用逻辑跟wx一样就不重复封装。然后把所有原生api在mpx对象上挂载为一个函数,但调用到时,在运行时判断,如果是当前平台重新封装过的api,就调用封装后的,如果未封装就看该平台是否原生支持该api,支持就调用原生,不支持直接抛错
一个例子比如微信支持nextTick,支付宝不支持,因此针对支付宝单独封装了nextTick: fn => setTimeout(fn)
生命周期
mpx里实际只用到了三种生命周期,页面就用到了onLoad,onReady,onUnload,组件就用到了attached,ready,detached,对应创建、渲染、卸载。但借助这几个生命周期和vue的响应式机制实现了以下三点:
-
各端小程序生命周期统一
把各端不同的生命周期都映射到了mpx的一个内部生命周期,比如把微信的detached和支付宝的didUnmount映射到了同一个内部生命周期destroyed,在代码里写都是写detached,但运行时会把detached作为destroyed属性缓存在组件对象上,最终在原生的detached或者didUnmount触发时调用destroyed属性指向的函数
-
扩展了部分生命周期
比如原生没有beforeCreate,但mpx会在原生的onload回调里触发beforeCreate,这样就扩展了生命周期。类似的还有beforeMount,beforeDestroy。updated生命周期是在属性变化时触发
性能优化
原生微信小程序一个主要的性能点在于setData的调用,mpx为了优化这一点采用了类vue的依赖收集、异步队列优化,并创新的使用了数据Diff、重写了setData
依赖收集:劫持数据,只有当渲染模版的数据发生变化时,才去重新setData,从而一些不影响模版渲染的数据变化不会导致setData,减少了setData的执行量
异步队列优化:采用vue了的机制,每个组件或页面的模版渲染函数的watcher都有其唯一的id,当数据变化导致模版应该重写渲染时,会把该watcher的执行放入一个队列,如有重复id的会直接移除,然后异步触发队列执行。这样同步设置数据的代码就只会导致一次模版渲染,从而减少setData的调用量
编译时生成render:还是像vue2那样将模版编译成ast,只不过生成render函数时,只取各种自定义指令和变量表达式的部分,从而把使用的变量路径都提取出来,并保持住逻辑关系,每次运行render函数,会把当前用到的变量路径和值缓存起来,在组件初始化时,会把传入的data配置项缓存起来,然后把render中收集的数据跟data做对比,只把变化过的数据最终传入setData。从而减少setData传输的数据量。当然每次传给setData后也会更新下data缓存
包体积优化
借助webpack的treeshaking机制,做到更好的包体积控制,编译前将目标平台设置为全局变量,在内部比如只有微信有的功能就使用if mode === wx包裹起来,如果编译目标是支付宝,这个等式不成立,在编译时if内代码就会被摇掉
同时使用webpack5,做到更优化的treeshaking