Open CommanderXL opened 4 years ago
小程序也是通过数据去驱动视图的渲染,需要手动的调用setData去完成这样一个动作。同时小程序的视图层也提供了用户交互的响应事件系统,在 js 代码中可以去注册相关的事件回调并在回调中去更改相关数据的值。Mpx 使用 Mobx 作为响应式数据工具并引入到小程序当中,使得小程序也有一套完成的响应式的系统,让小程序的开发有了更好的体验。
setData
还是从组件的角度开始分析 mpx 的整个响应式的系统。每次通过createComponent方法去创建一个新的组件,这个方法将原生的小程序创造组件的方法Component做了一层代理,例如在 attched 的生命周期钩子函数内部会注入一个 mixin:
createComponent
Component
// attached 生命周期钩子 mixin attached() { // 提供代理对象需要的api transformApiForProxy(this, currentInject) // 缓存options this.$rawOptions = rawOptions // 原始的,没有剔除 customKeys 的 options 配置 // 创建proxy对象 const mpxProxy = new MPXProxy(rawOptions, this) // 将当前实例代理到 MPXProxy 这个代理对象上面去 this.$mpxProxy = mpxProxy // 在小程序实例上绑定 $mpxProxy 的实例 // 组件监听视图数据更新, attached之后才能拿到properties this.$mpxProxy.created() }
在这个方法内部首先调用transformApiForProxy方法对组件实例上下文this做一层代理工作,在 context 上下文上去重置小程序的 setData 方法,同时拓展 context 相关的属性内容:
transformApiForProxy
this
function transformApiForProxy (context, currentInject) { const rawSetData = context.setData.bind(context) // setData 绑定对应的 context 上下文 Object.defineProperties(context, { setData: { // 重置 context 的 setData 方法 get () { return this.$mpxProxy.setData.bind(this.$mpxProxy) }, configurable: true }, __getInitialData: { get () { return () => context.data }, configurable: false }, __render: { // 小程序原生的 setData 方法 get () { return rawSetData }, configurable: false } }) // context 绑定注入的render函数 if (currentInject) { if (currentInject.render) { // 编译过程中生成的 render 函数 Object.defineProperties(context, { __injectedRender: { get () { return currentInject.render.bind(context) }, configurable: false } }) } if (currentInject.getRefsData) { Object.defineProperties(context, { __getRefsData: { get () { return currentInject.getRefsData }, configurable: false } }) } } }
接下来实例化一个 mpxProxy 实例并挂载至 context 上下文的 $mpxProxy 属性上,并调用 mpxProxy 的 created 方法完成这个代理对象的初始化的工作。在 created 方法内部主要是完成了以下的几个工作:
$watch
$forceUpdate
$updated
$nextTick
这里我们具体的来看下 initRender 方法内部是如何进行工作的:
export default class MPXProxy { ... initRender() { let renderWatcher let renderExcutedFailed = false if (this.target.__injectedRender) { // webpack 注入的有关这个 page/component 的 renderFunction renderWatcher = watch(this.target, () => { if (renderExcutedFailed) { this.render() } else { try { return this.target.__injectedRender() // 执行 renderFunction,获取渲染所需的响应式数据 } catch(e) { ... } } }, { handler: (ret) => { if (!renderExcutedFailed) { this.renderWithData(ret) // 渲染页面 } }, immediate: true, forceCallback: true }) } } ... }
在 initRender 方法内部非常清楚的看到,首先判断这个 page/component 是否具有 renderFunction,如果有的话那么就直接实例化一个 renderWatcher:
export default class Watcher { constructor (context, expr, callback, options) { this.destroyed = false this.get = () => { return type(expr) === 'String' ? getByPath(context, expr) : expr() } const callbackType = type(callback) if (callbackType === 'Object') { options = callback callback = null } else if (callbackType === 'String') { callback = context[callback] } this.callback = typeof callback === 'function' ? action(callback.bind(context)) : null this.options = options || {} this.id = ++uid // 创建一个新的 reaction this.reaction = new Reaction(`mpx-watcher-${this.id}`, () => { this.update() }) // 在调用 getValue 函数的时候,实际上是调用 reaction.track 方法,这个方法内部会自动执行 effect 函数,即执行 this.update() 方法,这样便会出发一次模板当中的 render 函数来完成依赖的收集 const value = this.getValue() if (this.options.immediateAsync) { // 放置到一个队列里面去执行 queueWatcher(this) } else { // 立即执行 callback this.value = value if (this.options.immediate) { this.callback && this.callback(this.value) } } } getValue () { let value this.reaction.track(() => { value = this.get() // 获取注入的 render 函数执行后返回的 renderData 的值,在执行 render 函数的过程中,就会访问响应式数据的值 if (this.options.deep) { const valueType = type(value) // 某些情况下,最外层是非isObservable 对象,比如同时观察多个属性时 if (!isObservable(value) && (valueType === 'Array' || valueType === 'Object')) { if (valueType === 'Array') { value = value.map(item => toJS(item, false)) } else { const newValue = {} Object.keys(value).forEach(key => { newValue[key] = toJS(value[key], false) }) value = newValue } } else { value = toJS(value, false) } } else if (isObservableArray(value)) { value.peek() } else if (isObservableObject(value)) { keys(value) } }) return value } update () { if (this.options.sync) { this.run() } else { queueWatcher(this) } } run () { const immediateAsync = !this.hasOwnProperty('value') const oldValue = this.value this.value = this.getValue() // 重新获取新的 renderData 的值 if (immediateAsync || this.value !== oldValue || isObject(this.value) || this.options.forceCallback) { if (this.callback) { immediateAsync ? this.callback(this.value) : this.callback(this.value, oldValue) } } } destroy () { this.destroyed = true this.reaction.getDisposer()() } }
Watcher 观察者核心实现的工作流程就是:
this.update()
mpx 在构建这个响应式的系统当中,主要有2个大的环节,其一为在构建编译的过程中,将 template 模块转化为 renderFunction,提供了渲染模板时所需响应式数据的访问机制,并将 renderFunction 注入到运行时代码当中,其二就是在运行环节,mpx 通过构建一个小程序实例的代理对象,将小程序实例上的数据访问全部代理至 MPXProxy 实例上,而 MPXProxy 实例即 mpx 基于 Mobx 去构建的一套响应式数据对象,首先将 data 数据转化为响应式数据,其次提供了 computed 计算属性,watch 方法等一系列增强的拓展属性/方法,虽然在你的业务代码当中 page/component 实例 this 都是小程序提供的,但是最终经过代理机制,实际上访问的是 MPXProxy 所提供的增强功能,所以 mpx 也是通过这样一个代理对象去接管了小程序的实例。需要特别指出的是,mpx 将小程序官方提供的 setData 方法同样收敛至内部,这也是响应式系统提供的基础能力,即开发者只需要关注业务开发,而有关小程序渲染运行在 mpx 内部去帮你完成。
小程序也是通过数据去驱动视图的渲染,需要手动的调用
setData
去完成这样一个动作。同时小程序的视图层也提供了用户交互的响应事件系统,在 js 代码中可以去注册相关的事件回调并在回调中去更改相关数据的值。Mpx 使用 Mobx 作为响应式数据工具并引入到小程序当中,使得小程序也有一套完成的响应式的系统,让小程序的开发有了更好的体验。还是从组件的角度开始分析 mpx 的整个响应式的系统。每次通过
createComponent
方法去创建一个新的组件,这个方法将原生的小程序创造组件的方法Component
做了一层代理,例如在 attched 的生命周期钩子函数内部会注入一个 mixin:在这个方法内部首先调用
transformApiForProxy
方法对组件实例上下文this
做一层代理工作,在 context 上下文上去重置小程序的 setData 方法,同时拓展 context 相关的属性内容:接下来实例化一个 mpxProxy 实例并挂载至 context 上下文的 $mpxProxy 属性上,并调用 mpxProxy 的 created 方法完成这个代理对象的初始化的工作。在 created 方法内部主要是完成了以下的几个工作:
$watch
,$forceUpdate
,$updated
,$nextTick
等方法,这样在你的业务代码当中即可直接访问实例上部署好的这些方法;这里我们具体的来看下 initRender 方法内部是如何进行工作的:
在 initRender 方法内部非常清楚的看到,首先判断这个 page/component 是否具有 renderFunction,如果有的话那么就直接实例化一个 renderWatcher:
Watcher 观察者核心实现的工作流程就是:
this.update()
方法来完成页面的重新渲染。mpx 在构建这个响应式的系统当中,主要有2个大的环节,其一为在构建编译的过程中,将 template 模块转化为 renderFunction,提供了渲染模板时所需响应式数据的访问机制,并将 renderFunction 注入到运行时代码当中,其二就是在运行环节,mpx 通过构建一个小程序实例的代理对象,将小程序实例上的数据访问全部代理至 MPXProxy 实例上,而 MPXProxy 实例即 mpx 基于 Mobx 去构建的一套响应式数据对象,首先将 data 数据转化为响应式数据,其次提供了 computed 计算属性,watch 方法等一系列增强的拓展属性/方法,虽然在你的业务代码当中 page/component 实例 this 都是小程序提供的,但是最终经过代理机制,实际上访问的是 MPXProxy 所提供的增强功能,所以 mpx 也是通过这样一个代理对象去接管了小程序的实例。需要特别指出的是,mpx 将小程序官方提供的 setData 方法同样收敛至内部,这也是响应式系统提供的基础能力,即开发者只需要关注业务开发,而有关小程序渲染运行在 mpx 内部去帮你完成。