跟踪嵌套数据结构的另一种方法

@tracked 状态下的数组或对象在 Glimmer 组件中,当您更改数组/对象中的某个成员时,不会触发更新。

此主题讨论了一些选项 以使您的视图正确更新。我想添加另一种方法,因为那里讨论的选项对我不起作用。

我的解决方法是:与其一次分配一个元素,不如一次性分配整个数组。这种相同的策略也适用于对象或任何其他数据结构。

在 Glimmer 组件中:

@tracked categories = [];

constructor() {
  super(...arguments);
  this.loadAllData();
}

async loadAllData() {
  const categories = []
  const waiting = this.siteSettings.county_fence_print_categories.split('|').map(async (cid, index) => {
    const data = await this.loadData(`/c/${cid}/l/latest.json?period=monthly`)
    return data.topic_list.topics
  })

  // 这是需要注意的行
  // 因为我们直接对数组进行赋值,它将触发更新。
  // 而推送到数组或按索引设置元素则不会触发更新。
  this.categories = await Promise.all(waiting)
}

async loadData(url) {
  try {
    const data = await ajax(url);
    //console.log(`Got data for ${url}:`, data)
    return data
  } catch (error) {
    console.error(`Failed to get data for ${url}:`, error);
  }
}

您也可以通过部分更新来完成此操作。重要的是让 JavaScript 检测到正在分配不同的值。因此,虽然 .concat 会起作用,因为它会生成一个与旧数组不同的新数组:

this.categories = this.categories.concat(newValue)

使用 .push 可能不会起作用,因为结果数组与原始数组具有相同的对象 ID,因此 JavaScript 认为没有任何变化。

this.categories.push(newValue)
this.categories = this.categories

如果您正在处理一个对象,生成新对象的简单方法是使用 Object.assign() - 它会创建一个新的、扩展的数据结构,就像上面的 .concat 一样。

对于 Vue.js 等其他框架来说,这早已是常态——您可以找到关于此的讨论,可以追溯到 10 年前。这与 JavaScript 中的实现方式有关,可能是父对象上的 getter/setter 或 observe API。但如果它是通过 observe API 实现的,那么对于这种行为的借口就少一些了,因为可以轻松切换 subtree 标志…也许他们出于性能考虑而选择不这样做,当观察深度嵌套的结构时?但跑题了,这些是底层库的实现细节。上面的解决方法应该可以帮助您解决这个问题。

2 个赞

我想知道 TrackedArray 是否能在此处提供帮助?
我没有深入研究。我只记得 Discourse 代码在这里和那里都使用了它。

import { TrackedArray, TrackedObject } from “@ember-compat/tracked-built-ins”;

2 个赞

要是这份文档有记录就好了。 :laughing:

不过是的,这似乎正是为这种情况设计的。

2 个赞