# 高级数据更新方法

组件框架支持几种更新数据的方式。

## 使用 setData 更新数据

setData 是最经典的更新数据方法。在调用 setData 时，组件框架会做两件事情：

1. 立刻将变更的数据改到组件数据（即 `this.data`）上；
2. 将新的数据应用到 WXML 模板上、更新界面。

setData 对 WXML 模板的更新是 **同步** 的（除了在数据监听器内被调用时）。换而言之，调用完 setData 后，可以视为 WXML 立刻被更新完毕了，可以立刻调用 [组件树访问](component-tree-visit) 和 [获取界面上的节点信息](/miniprogram/dev/framework/view/selector) 等相关方法。

除了最普通的用法外，`setData` 还可以使用数据路径来更新 data 中的某个子数据字段，例如：

```js
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()
```

[在开发者工具中预览效果](https://developers.weixin.qq.com/s/VZ7RTvmg80b5)

setData 的第二个参数（可选）是一个回调函数，它在这组数据完全应用到渲染引擎后回调。这个回调函数的回调时机偏晚，滥用可能会影响性能，请谨慎使用。

```js
this.setData({}, () => {
  // 完全应用到渲染引擎后回调
})
```

[在开发者工具中预览效果](https://developers.weixin.qq.com/s/X9411Bmy8Ubk)

## 高级路径更新

如果想仅更新对象内的一个字段，可以使用高级路径更新的方式。这样虽然接口复杂一些，但可以拥有更好的性能。

可以使用 `replaceDataOnPath` 来更新对象内的数据字段，例如：

```js
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()
```

[在开发者工具中预览效果](https://developers.weixin.qq.com/s/cu7qPvmb80bn)

> `spliceArrayDataOnPath` 仅 glass-easel 组件框架支持。

如果字段是数组类型的，还可以使用 `spliceArrayDataOnPath` 来对数组项进行插入和删除，例如：

```js
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()
```

[在开发者工具中预览效果](https://developers.weixin.qq.com/s/957FRvmk80bc)

调用 `replaceDataOnPath` 和 `spliceArrayDataOnPath` 后，并不会立即将更新内容应用到 WXML 模板上，还需要调用 `applyDataUpdates`。如果需要连续调用多个 `replaceDataOnPath` 或 `spliceArrayDataOnPath`，在末尾调用一次 `applyDataUpdates` 即可。

## 组合更新

> `groupUpdates` 仅 glass-easel 组件框架支持。

连续调用多个 `replaceDataOnPath` 或 `spliceArrayDataOnPath` 后，可能会遗忘调用 `applyDataUpdates`。

可以考虑改用 `groupUpdates` 将它们组合起来，例如：

```js
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()
```

[在开发者工具中预览效果](https://developers.weixin.qq.com/s/W074Yvmt80bl)

在 `groupUpdates` 回调函数返回后，会自动将更新应用到 WXML 模板上，不再需要调用 `applyDataUpdates`。

## 使用 updateData 更新

> `updateData` 仅 glass-easel 组件框架支持。

`updateData` 调用语法类似于 `setData`，但它不会立刻将更新应用到 WXML 模板上，需要额外调用 `applyDataUpdates` 和 `groupUpdates`。

如果需要连续多次调用 `setData`，推荐改用 `updateData`，并结合 `groupUpdates` 来将它们组合起来。这种方式性能显著优于多次调用 setData。例如： 

```js
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()
```

[在开发者工具中预览效果](https://developers.weixin.qq.com/s/9G78WvmA80bS)

此外，在 [数据监听器](/miniprogram/dev/framework/observer) 内使用 setData 时，推荐使用 `updateData` 代替：因为数据监听内的 `setData` 相当于 `updateData`。

## 使用 groupSetData 连续更新

有时候，连续两个 `setData` 不能被替换为 `groupUpdates` + `updateData`。典型的情况是，第一个 setData 更新 `<scroll-view>` 内部的节点列表，第二个 setData 需要更新滚动位置。这种时候必须用两个 setData 来分别更新。

但这样可能导致第一次 setData 更新后、第二次 setData 更新前的界面中间状态展示给用户。此时可以用 `groupSetData` 将两个 setData 组合起来，确保界面中间状态不要展示给用户。用法例如：

```js
this.groupSetData(() => {
  this.setData({ list })
  this.setData({ scrollPosition })
})
```

[在开发者工具中预览效果](https://developers.weixin.qq.com/s/sr7pSvmb80bo)

## 递归更新问题

调用 `setData`、`applyDataUpdates`，或当 `groupUpdates` 回调函数执行完毕的时刻，新的组件数据应用到 WXML 模板上。这是个同步的过程。

在这个同步过程中，更新执行到一半的时候，可能先暂停父组件的更新、进入到子组件内部、让子组件先执行完更新，再回到父组件继续更新。如果此时，子组件又通过某些方法再次触发了父组件的更新，会导致父组件同时进行两个更新，会导致 WXML 模版混乱。

最常见的一种情况是：

1. 父组件更新了子组件的某个属性；
2. 子组件触发了 [数据监听器](/miniprogram/dev/framework/observer) 或 [属性 observer](properties#属性值变化监听器) ；
3. 在监听器回调里面触发事件；
4. 父组件又同步调用了一次 setData（或其他数据更新方法）。

避免这种问题的方法是：适当使用 `wx.nextTick` 避免递归更新，可参考 [在独立任务中触发事件](event-system#在独立任务中触发事件) 的做法。
