miniprogram-computed之watch源码解析
前言
在上一章介绍了computed的基本使用与源码解析,本章继续学习关于watch的基本使用与源码解析。
基本使用
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
}
},
watch: {
'a, b.**' (...newVals) {
// [2, { c:1, d: [2, 3] }]
console.log(newVals)
},
total (newVal) {
// 3
console.log(newVal)
}
},
attached() {
this.setData({
a: 2
})
}
})
watch源码解析
上一章介绍了源码中会监听_computedWatchInit属性,在created和attached生命周期中做一些初始化操作。
在created钩子中,会声明computedWatchInfo监听对象,用于保存一下依赖信息。
// 初始化当前组件的监听对象
const computedWatchInfo = {
// 保存包装后的计算函数
computedUpdaters: [],
// 根据computed属性值保存计算过程中依赖的data中的key及value
computedRelatedPathValues: {},
// 保存了watch属性到value值的映射
watchCurVal: {},
// 用于标识attached钩子中,初始化执行了哪些computed属性
_triggerFromComputedAttached: {}
}
// 保存到根实例_computedWatchInfo属性中
if (!this._computedWatchInfo) this._computedWatchInfo = {}
// computedWatchDefId 持续自增的数字
this._computedWatchInfo[computedWatchDefId] = computedWatchInfo
关键属性介绍
watchCurVal
保存了watch属性到value值的映射
如上文基本使用中的事例,监听了a属性以及b属性所有子数据的变化,则初始化执行之后watchCurVal的值为
// created
computedWatchInfo.watchCurVal = {
'a, b.**': [
1,
{ c: 1, d: [2, 3] }
],
'total': [null]
}
_triggerFromComputedAttached
用于标识attached钩子中,初始化执行了哪些computed属性。
如上文基本使用中的事例,初始化执行computed之后,_triggerFromComputedAttached的值为
// attached
computedWatchInfo._triggerFromComputedAttached = {
'total': true
}
watch初始化
created钩子函数中除了初始化监听对象之外,还对用户传入的watch属性执行了初始化操作。
(1)首先调用parseMultiDataPaths方法,解析出path数组,以及对应的options,针对深度监听,对应的deepCmp属性为true。
deepCmp属性在下文中主要用来区分比较新旧数据是否发生变化的计算方法,为true则执行深比较,为false在执行浅比较。
(2)计算出当前监听属性的val值,类型为数组。
(3)保存watch属性与value值得映射。
Object.keys(watchDef).forEach((watchPath) => {
// watchPath: 'a.**, b.c'
// parseMultiDataPaths解析结果
// [
// { path: [ 'a' ], options: { deepCmp: false } },
// { path: [ 'b' ], options: { deepCmp: true } }
// ]
const paths = dataPath.parseMultiDataPaths(watchPath)
// record the original value of watch targets
// 当前path 对应的value值
const curVal = paths.map(({ path, options }) => {
// 根据数组路径,从data中获取value
const val = dataPath.getDataOnPath(this.data, path)
// deepCmp为true 深度克隆
return options.deepCmp ? deepClone(val) : val
})
// 将watchPath与value保存到watchCurVal中
computedWatchInfo.watchCurVal[watchPath] = curVal
})
监听执行
(1)遍历watch属性,利用observers执行监听。
Object.keys(watchDef).forEach((watchPath) => {
observersItems.push({
fields: watchPath,
observer(this: BehaviorExtend) {
// 利用observers执行监听
}
})
})
(2)判断当前函数的执行是否是由computed属性,在attached钩子中初始化造成的,如果是,直接返回,不走第三步的新旧值判断。
computed在attached钩子中初始化时,往_triggerFromComputedAttached属性中保存了当前属性值,为将值置为true。
在computed执行setData放入data中时,如果watch中监听了该computed属性(如基本使用事例,监听了total属性),此时total的Watcher将会执行,当查询到_triggerFromComputedAttached.total为true时,知道此处是由computed初始化引起的,便中止执行。
const paths = dataPath.parseMultiDataPaths(watchPath)
// (issue #58) ignore watch func when trigger by computed attached
if (
Object.keys(computedWatchInfo._triggerFromComputedAttached).length
) {
const pathsMap: Record<string, boolean> = {}
// { a: true, b: true }
paths.forEach((path) => (pathsMap[path.path[0]] = true))
for (const computedVal in computedWatchInfo._triggerFromComputedAttached) {
if (computedWatchInfo._triggerFromComputedAttached.hasOwnProperty(computedVal)) {
// 包含computed属性
if (
pathsMap[computedVal] &&
computedWatchInfo._triggerFromComputedAttached[computedVal]
) {
// 置为false
computedWatchInfo._triggerFromComputedAttached[
computedVal
] = false
// 拦截下方watch原函数执行
return
}
}
}
}
(3)判断新旧值是否不同,不同则执行watch原函数
取出保存在watchCurVal中的旧值之后,计算出新值,并按照deepCmp属性,来选择进行深比较还是浅比较,如果值不同,则执行原watcher函数,并传入新值。
深比较:以递归的方式遍历两个对象的所有属性是否相等,不管这两个对象是不是同一对象的引用。
浅比较:即引用比较 ===
// 取出缓存的旧值
const oldVal = computedWatchInfo.watchCurVal[watchPath]
// 获取新的值与options配置项
const originalCurValWithOptions = paths.map(({ path, options }) => {
const val = dataPath.getDataOnPath(this.data, path)
return { val, options }
})
// 获取新值
const curVal = originalCurValWithOptions.map(({ val, options }) =>
options.deepCmp ? deepClone(val) : val,
)
// 保存新值
computedWatchInfo.watchCurVal[watchPath] = curVal
// 比较新旧值
let changed = false
for (let i = 0; i < curVal.length; i++) {
const options = paths[i].options
const deepCmp = options.deepCmp
// deepCmp为true走深比较,否则走浅比较
if (
deepCmp
// 深比较
? !deepEqual(oldVal[i], curVal[i])
// 浅比较
: !equal(oldVal[i], curVal[i])
) {
changed = true
break
}
}
// changed为true 说明需要执行原函数
if (changed) {
watchDef[watchPath].apply(
this,
// 传入新值
originalCurValWithOptions.map(({ val }) => val)
)
}
转载自:https://juejin.cn/post/7217734962107072572