原生小程序批量队列更新数据

1,959 阅读2分钟

在原生小程序开发中, setData 是使用最频繁的接口,也是最容易引发性能问题的接口。因为小程序的视图层和逻辑层是两个独立的模块,它们之间的通讯由 evaluateJavascript 实现,而 evaluateJavascript 的执行会受很多方面的影响,所以优化 setData 是小程序性能优化的关键部分。同时市面上的各种小程序开发框架也都对 setData 做了自动化的批量更新处理,减少 setData 调用次数以进行性能优化,在原生小程序开发中也应该如此。

在开发中经常会遇到如下类似的场景,需要根据条件决定是否对某些数据进行 setData 操作:

const { name, age } = userInfo
if (name) {
  this.setData({ name })
}
if (age) {
  this.setData({ age })
}

我们调用了2setData 方法,其实可以只调用一次,手动优化下:

const { name, age } = userInfo
const setDataObj = {}
if (name) {
  setDataObj.name = name
}
if (age) {
  setDataObj.age = age
}
this.setData(setDataObj)

这才是我们想要的结果,只执行一次 setData 操作。

对于简单的场景我们可以进行手动优化,但是对于复杂的场景,手动优化就会比较麻烦。 对此,我们可以利用 Event Loop 机制,实现自动优化。关于 Event Loop 相关介绍,网上有很多优秀的资料,在此不再赘述。

先上实现:

/**
 * 批量更新
 * @param {Record<string, any>} thisArg - 页面(或组件)的 this 对象
 * @param {boolean} [override=true] - 是否覆写 this.setData 方法
 */
function batchUpdates(thisArg, override = true) {
  const queue = {
    data: null,
    callbacks: [],
  }

  const setData = thisArg.setData.bind(thisArg)

  const exec = () => {
    if (!queue.data) return

    const { data, callbacks } = queue
    queue.data = null
    queue.callbacks = []
    setData(data, () => {
      let cb
      while ((cb = callbacks.shift())) cb()
    })
  }

  const push = (data, callback) => {
    queue.data = { ...queue.data, ...data }
    if (typeof callback === 'function') {
      queue.callbacks.push(callback)
    }

    // 核心
    Promise.resolve().then(exec)
  }

  if (override) {
    thisArg.setData = push
    thisArg.$forceUpdate = exec
    thisArg.$originSetData = setData
  }

  return { push, forceUpdate: exec }
}

使用方式一(推荐):

// 首先引入 batchUpdates
Page({
  onLoad() {
    // 在 onLoad 或 attached 生命周期函数中进行 this 绑定
    batchUpdates(this)

    // 之后的 this.setData 会自动进行批量更新操作
    this.setData({ name: 'name1' })
    this.setData({ name: 'name2' })
  },
})

该方式会覆写原生的 setData 方法,之后调用 this.setData 方法会自动进行批量更新处理。如果想获取原生的 setData 方法,可以使用 this.$originSetData

使用方式二:

// 首先引入 batchUpdates
Page({
  onLoad() {
    // 在 onLoad 或 attached 生命周期函数中进行 this 绑定
    this.batchUpdates = batchUpdates(this, false)

    this.batchUpdates.push({ name: 'name1' }, () => {
      console.log('callback')
    })
  },
})

该方式不会对 this 对象做任何修改,后续需要使用 batchUpdates 返回的对象进行操作。

注:在开发中可能会有如下操作:

this.setData({ name: 'new name' })
console.log(this.data.name)

因为小程序逻辑层是同步的,视图层是异步的,所以如上操作获取的 name 值是 new name 。使用批量更新后,更新数据会变成异步的,上述操作获取的 name 值仍然是之前的值。如果想获取更新之后的值,可以在 this.setDatabatchUpdates.push 的回调函数中进行获取。如果想立刻执行更新,可以使用 this.$forceUpdate()batchUpdates.forceUpdate()

以上便是自己对原生小程序中批量更新数据的简单实现。