共计 5168 个字符,预计需要花费 13 分钟才能阅读完成。
前言
用惯了 vue 的 computed 属性,在小程序开发中不免也会想着应用,而官网正好提供了 miniprogram-computed 来在小程序中应用 computed 属性。本着精进技术的准则,一起来瞅瞅官网的源码吧!
根本应用
// js 代码
import {behavior as computedBehavior} from 'miniprogram-computed'
Component({behaviors: [ computedBehavior],
data: {
a: 1,
b: {
c: 1,
d: [2, 3]
}
},
computed: {total(data) {return data.a + data.b.c + data.b.d[0]
}
}
})
// 页面中应用
// <view>{{total}}</view>
源码解析
1. 初始化 computedWatchInfo 对象
在 created 生命周期中初始化了 computedWatchInfo 对象,并保留到页面根实例上。
<span style=”color:rgb(236, 68, 68);”>computedWatchInfo</span> 对象保护了以后组件实例的 computed 计算信息。
<span style=”color:rgb(236, 68, 68);”>computedUpdaters 数组 </span> 保留了包装后的计算函数,每当 data 值变动后,就执行其中保留的包装函数,并依据 computedRelatedPathValues 中收集的依赖关系,来判断是否要执行原始计算函数。
<span style=”color:rgb(236, 68, 68);”>computedRelatedPathValues</span> 对象保留了 computed 属性值与计算函数运算过程中所依赖的 key 与 keyValue 的映射关系,用于前期 data 值变动后,通过查找依赖值是否变动,来决定是否进行从新计算。
// 初始化以后组件的监听对象
const computedWatchInfo = {
// 保留包装后的计算函数
computedUpdaters: [],
// 依据 computed 属性值保留计算过程中依赖的 data 中的 key 及 value
computedRelatedPathValues: {},
watchCurVal: {},
_triggerFromComputedAttached: {}}
// 保留到根实例_computedWatchInfo 属性中
if (!this._computedWatchInfo) this._computedWatchInfo = {}
// computedWatchDefId 继续自增的数字
this._computedWatchInfo[computedWatchDefId] = computedWatchInfo
2. 遍历 computed 对象将 key 及 computedValue 设置到 data 中
在 attached 生命周期中,遍历所有的 computed 属性及对应的计算函数,执行计算函数并传入代理后的 data 对象,将 key 及计算后的 value 值通过 setData 设置到 data 中。
(1)在 attached 生命周期中,遍历 computed,拿到所有的计算函数,进行 computed 值的初始化,并对每个计算函数所依赖的 data 属性值进行依赖关联关系收集,并将收集后的关系表保留到监听对象 computedRelatedPathValues 对象中。
(2)<span style=”color:rgb(236, 68, 68);”>dataTracer.create(this.data, relatedPathValuesOnDef)</span> 这个办法的执行比拟重要,它即通过 Proxy 对 data 对象进行了代理,也在 getter 办法中,收集了计算函数运行时,所依赖的 data 数值,并建设了依赖关联关系表,用于前期判断是否须要从新执行计算函数。
(3)将依赖关联关系表存到了 computedRelatedPathValues 属性上。
// 计算方法 function
const updateMethod = computedDef[targetField]
// 依赖关联关系表
const relatedPathValuesOnDef = []
// 返回计算后的 val
const val = updateMethod(
// 传入用户定义的 data 属性对象
// 返回了代理的 data 对象
dataTracer.create(this.data, relatedPathValuesOnDef)
)
const pathValues = relatedPathValuesOnDef.map(({path}) => ({
path,
// 依据数组门路,从 data 中获取 value
value: dataPath.getDataOnPath(this.data, path)
}))
// 将 computed 属性与计算值设置到 data 中
this.setData({
// 定义的 computed 属性 返回实在的的 val
[targetField]: dataTracer.unwrap(val)
})
computedWatchInfo._triggerFromComputedAttached[targetField] = true
// 保留每个计算函数的依赖关联关系表
computedWatchInfo.computedRelatedPathValues[targetField] = pathValues
(4)设置包装函数并保留到监听对象 computedUpdaters 属性中,函数外部会取到各自计算函数所依赖的关联关系表,比拟旧的关联关系表中,是否有 value 值产生了变动,如果有,则阐明须要从新计算,此时则调用原始的计算函数获取新的 computed 值,并设置新的依赖关联关系表。
// 设置包装函数
const updateValueAndRelatedPaths = () => {
// 取到旧的依赖关联关系
const oldPathValues = computedWatchInfo.computedRelatedPathValues[targetField]
// 默认不须要更新
let needUpdate = false
// 查找旧的依赖关系中,是否有值产生了变动
for (let i = 0; i < oldPathValues.length; i++) {const { path, value: oldVal} = oldPathValues[i]
const curVal = dataPath.getDataOnPath(this.data, path)
// 新值与旧值不相等 阐明须要更新
if (!equal(oldVal, curVal)) {
needUpdate = true
break
}
}
// 不须要更新就暂停
if (!needUpdate) return false
// 这里阐明须要更新
// 新的依赖关联关系
const relatedPathValues = []
// 取到新值
const val = updateMethod(
// 传入代理对象
dataTracer.create(this.data, relatedPathValues),
)
// 更新 computed key value
this.setData({[targetField]: dataTracer.unwrap(val),
})
// 再次拿到新的依赖关联关系
const pathValues = relatedPathValues.map(({path}) => ({
path,
// 区别就是从原始对象上拿到了 value 值
value: dataPath.getDataOnPath(this.data, path),
}))
// 再次保留新的依赖关联关系
computedWatchInfo.computedRelatedPathValues[targetField] = pathValues
return true
}
// 更新办法保留在这里
computedWatchInfo.computedUpdaters.push(updateValueAndRelatedPaths,)
3. 设置 observers 监听
(1)往 observersItems 数组中退出监听属性_computedWatchInit,别离在 created 和 attached 生命周期中触发上述第一步及第二步的执行。
attached(this: BehaviorExtend) {
this.setData({_computedWatchInit: ComputedWatchInitStatus.ATTACHED,})
}
created(this: BehaviorExtend) {
this.setData({_computedWatchInit: ComputedWatchInitStatus.CREATED,})
}
// 执行初始化
observersItems.push({
fields: '_computedWatchInit',
observer(this: BehaviorExtend) {
// 拿到以后的组件执行状态
const status = this.data._computedWatchInit
if (status === ComputedWatchInitStatus.CREATED) {// 执行第一步} else if (status === status === ComputedWatchInitStatus.ATTACHED) {// 执行第二步}
}
})
(2)监听 data 中的所有属性,每当属性值变动时,找到对象的组件实例对应的监听对象,对监听对象的 computedUpdaters 数组 (保留的包装后的计算函数)进行遍历执行。
// 监听所有属性
observersItems.push({
fields: '**',
observer(this: BehaviorExtend) {if (!this._computedWatchInfo) return
// 取出以后组件的 computed 信息
const computedWatchInfo = this._computedWatchInfo[computedWatchDefId]
// 没有则返回
if (!computedWatchInfo) return
// 将所有须要更新的 computed 从新进行计算
let changed: boolean
do {changed = computedWatchInfo.computedUpdaters.some((func) =>
func.call(this),
)
} while (changed)
}
})
(3)将 observersItems 数组中保留的监听配置,放入到 observers 对象中去。
// 初始化 observers
if (typeof defFields.observers !== 'object') {defFields.observers = {}
}
// 包装监听函数
if (Array.isArray(defFields.observers)) {defFields.observers.push(...observersItems)
} else {
// 这边做了兼容 如果曾经监听 就把两个监听函数合并起来
observersItems.forEach((item) => {// defFields.observers[item.fields] = item.observer
const f = defFields.observers[item.fields]
if (!f) {defFields.observers[item.fields] = item.observer
} else {defFields.observers[item.fields] = function () {item.observer.call(this)
f.call(this)
}
}
})
}
Vue 与小程序中的 computed 应用区别
- 1.Vue 中 computed 计算函数中的 this 指向以后组件实例,而小程序为 undefined,只能通过拜访传入的代理 data 对象,来进行值的运算。
- 2.Vue 中的 computed 属性值只有在拜访时,才进行值运算,而小程序中的 computed 无论页面中是否应用,都会进行首次值的运算。
- 3.Vue 中的 computed 属性值外部通过 dirty 值进行脏值测验,依赖属性发生变化后,会批改 dirty 值,当再次读取 computed 属性值时,因为 dirty 产生了变动,则会进行从新计算。而小程序中是通过监听所有的 data 属性值,当任意值发生变化后,遍历执行所有的包装计算方法,如果该办法所依赖的 data 属性值产生了变动,则从新计算,并通过 setData 进行赋值操作,触发页面更新。