前言

用惯了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属性上。

// 计算方法 functionconst updateMethod = computedDef[targetField]// 依赖关联关系表const relatedPathValuesOnDef = []// 返回计算后的valconst 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 valuethis.setData({    [targetField]: dataTracer.unwrap(val),})// 再次拿到新的依赖关联关系const pathValues = relatedPathValues.map(({ path }) => ({    path,    // 区别就是从原始对象上拿到了value值    value: dataPath.getDataOnPath(this.data, path),}))// 再次保留新的依赖关联关系computedWatchInfo.computedRelatedPathValues[targetField] = pathValuesreturn 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对象中去。

// 初始化observersif (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进行赋值操作,触发页面更新。