# 高级数据更新方法
组件框架支持几种更新数据的方式。
# 使用 setData 更新数据
setData 是最经典的更新数据方法。在调用 setData 时,组件框架会做两件事情:
- 立刻将变更的数据改到组件数据(即
this.data)上; - 将新的数据应用到 WXML 模板上、更新界面。
setData 对 WXML 模板的更新是 同步 的(除了在数据监听器内被调用时)。换而言之,调用完 setData 后,可以视为 WXML 立刻被更新完毕了,可以立刻调用 组件树访问 和 获取界面上的节点信息 等相关方法。
除了最普通的用法外,setData 还可以使用数据路径来更新 data 中的某个子数据字段,例如:
export default Component()
.data(() => ({
obj: {
foo: [1, 2],
},
}))
.init(function ({ setData, lifetime }) {
lifetime('attached', () => {
// 将 data.obj.a[0] 设置为 3
setData({
'obj.foo[0]': 3,
})
})
})
.register()
setData 的第二个参数(可选)是一个回调函数,它在这组数据完全应用到渲染引擎后回调。这个回调函数的回调时机偏晚,滥用可能会影响性能,请谨慎使用。
this.setData({}, () => {
// 完全应用到渲染引擎后回调
})
# 高级路径更新
如果想仅更新对象内的一个字段,可以使用高级路径更新的方式。这样虽然接口复杂一些,但可以拥有更好的性能。
可以使用 replaceDataOnPath 来更新对象内的数据字段,例如:
export default Component()
.data(() => ({
obj: {
foo: [1, 2],
},
}))
.init(function ({ lifetime }) {
lifetime('attached', () => {
// 将 data.obj.a[0] 设置为 3
// (等价于 setData 'obj.foo[0]')
this.replaceDataOnPath(['obj', 'foo', 0], 3)
// 将更新应用到模板上
this.applyDataUpdates()
})
})
.register()
spliceArrayDataOnPath仅 glass-easel 组件框架支持。
如果字段是数组类型的,还可以使用 spliceArrayDataOnPath 来对数组项进行插入和删除,例如:
export default Component()
.data(() => ({
obj: {
foo: [1, 2, 3, 4],
},
}))
.init(function ({ lifetime }) {
lifetime('attached', () => {
// 类似于数组的 splice 方法
// 可以在一个位置上移除若干项,再插入若干项
this.spliceArrayDataOnPath(['obj', 'foo'], 1, 2, [5, 6, 7])
// 得到的 obj.foo 是 [1, 5, 6, 7, 4]
// 将更新应用到模板上
this.applyDataUpdates()
})
})
.register()
调用 replaceDataOnPath 和 spliceArrayDataOnPath 后,并不会立即将更新内容应用到 WXML 模板上,还需要调用 applyDataUpdates。如果需要连续调用多个 replaceDataOnPath 或 spliceArrayDataOnPath,在末尾调用一次 applyDataUpdates 即可。
# 组合更新
groupUpdates仅 glass-easel 组件框架支持。
连续调用多个 replaceDataOnPath 或 spliceArrayDataOnPath 后,可能会遗忘调用 applyDataUpdates。
可以考虑改用 groupUpdates 将它们组合起来,例如:
export default Component()
.data(() => ({
obj: {
foo: [1, 2],
},
}))
.init(function ({ lifetime }) {
lifetime('attached', () => {
this.groupUpdates(() => {
this.replaceDataOnPath(['obj', 'foo', 0], 3)
this.replaceDataOnPath(['obj', 'foo', 1], 4)
})
})
})
.register()
在 groupUpdates 回调函数返回后,会自动将更新应用到 WXML 模板上,不再需要调用 applyDataUpdates。
# 使用 updateData 更新
updateData仅 glass-easel 组件框架支持。
updateData 调用语法类似于 setData,但它不会立刻将更新应用到 WXML 模板上,需要额外调用 applyDataUpdates 和 groupUpdates。
如果需要连续多次调用 setData,推荐改用 updateData,并结合 groupUpdates 来将它们组合起来。这种方式性能显著优于多次调用 setData。例如:
export default Component()
.data(() => ({
a: 1,
b: 2,
}))
.init(function ({ lifetime }) {
lifetime('attached', () => {
// 用 groupUpdates 将多个 updateData 组合起来
this.groupUpdates(() => {
this.updateData({ a: 3 })
this.updateData({ b: 4 })
})
// 这样做的性能优于连续多次调用 setData
})
})
.register()
此外,在 数据监听器 内使用 setData 时,推荐使用 updateData 代替:因为数据监听内的 setData 相当于 updateData。
# 使用 groupSetData 连续更新
有时候,连续两个 setData 不能被替换为 groupUpdates + updateData。典型的情况是,第一个 setData 更新 <scroll-view> 内部的节点列表,第二个 setData 需要更新滚动位置。这种时候必须用两个 setData 来分别更新。
但这样可能导致第一次 setData 更新后、第二次 setData 更新前的界面中间状态展示给用户。此时可以用 groupSetData 将两个 setData 组合起来,确保界面中间状态不要展示给用户。用法例如:
this.groupSetData(() => {
this.setData({ list })
this.setData({ scrollPosition })
})
# 递归更新问题
调用 setData、applyDataUpdates,或当 groupUpdates 回调函数执行完毕的时刻,新的组件数据应用到 WXML 模板上。这是个同步的过程。
在这个同步过程中,更新执行到一半的时候,可能先暂停父组件的更新、进入到子组件内部、让子组件先执行完更新,再回到父组件继续更新。如果此时,子组件又通过某些方法再次触发了父组件的更新,会导致父组件同时进行两个更新,会导致 WXML 模版混乱。
最常见的一种情况是:
- 父组件更新了子组件的某个属性;
- 子组件触发了 数据监听器 或 属性 observer ;
- 在监听器回调里面触发事件;
- 父组件又同步调用了一次 setData(或其他数据更新方法)。
避免这种问题的方法是:适当使用 wx.nextTick 避免递归更新,可参考 在独立任务中触发事件 的做法。