在原生小程序开发中, setData 是使用最频繁的接口,也是最容易引发性能问题的接口。因为小程序的视图层和逻辑层是两个独立的模块,它们之间的通讯由 evaluateJavascript 实现,而 evaluateJavascript 的执行会受很多方面的影响,所以优化 setData 是小程序性能优化的关键部分。同时市面上的各种小程序开发框架也都对 setData 做了自动化的批量更新处理,减少 setData 调用次数以进行性能优化,在原生小程序开发中也应该如此。
在开发中经常会遇到如下类似的场景,需要根据条件决定是否对某些数据进行 setData 操作:
const { name, age } = userInfo
if (name) {
this.setData({ name })
}
if (age) {
this.setData({ age })
}
我们调用了2次 setData 方法,其实可以只调用一次,手动优化下:
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.setData或batchUpdates.push的回调函数中进行获取。如果想立刻执行更新,可以使用this.$forceUpdate()或batchUpdates.forceUpdate()。
以上便是自己对原生小程序中批量更新数据的简单实现。