Glimmer 组件更改检测未生效 - @tracked 数组中属性突变

大家好!

我在 Glimmer 组件中遇到变更检测问题,需要一些指导。当我修改 @tracked 数组中对象的属性时,模板不会重新渲染。

我的设置:

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { fn } from '@ember/helper';
import { on } from '@ember/modifier';
import { action } from '@ember/object';

export default class CustomSidebarComponent extends Component {
  @tracked items = [
    {
      id: 'home',
      label: 'Home',
      expanded: false
    },
    {
      id: 'my-posts',
      label: 'My Posts',
      expanded: false
    }
    // ... more items
  ];

  @action
  toggleExpanded(item) {
    item.expanded = !item.expanded; // 此变异不会触发重新渲染
    console.log('Toggled:', item.label, item.expanded); // 正确记录
  }

  <template>
    <div id="custom-sidebar">
      <ul>
        {{#each this.items key="@index" as |item|}}
          <li class="menu-item {{if item.expanded 'expanded'}}" {{on "click" (fn this.toggleExpanded item)}}>
            {{item.label}} {{if item.expanded "(expanded)" ""}}
          </li>
        {{/each}}
      </ul>
    </div>
  </template>
}

问题:

  1. 点击处理程序正常执行
  2. item.expanded 属性已更新(已在控制台中验证)
  3. 但模板未重新渲染 - 没有类更改,没有文本更改

我尝试过的方法:

  1. 数组重新分配 - 变异后 this.items = [...this.items](无效)
  2. Immer 用于不可变更新 - 想尝试这个,但无法在 Discourse 主题中使 npm 导入正常工作
  3. 不同的键策略 - key="id" 而不是 key="@index"

问题:

  • @tracked 数组中修改对象属性是否应该在 Glimmer 中工作?
  • 我是否需要以某种方式跟踪单个对象?
  • 这个用例是否有推荐的模式?
  • 这是否与在 Discourse 主题环境中运行有关?
  • Discourse 主题中的 npm 导入是否存在影响响应式库的限制?

环境:

  • Discourse 主题组件
  • 具有 .gjs 文件的 Glimmer 组件
  • 最新的 Discourse 版本 - 今天更新

非常感谢任何见解!我是否忽略了 Glimmer 响应式系统的某些基本内容?

谢谢!

2 个赞

嗯,那很奇怪。数组赋值技巧通常是有效的。

我发现的一个替代方法是让数组中的对象来自一个具有自己跟踪属性的类。例如:

class CustomSidebarItem {
  @tracked expanded = false;
  constructor(id, label) {
    this.id = id;
    this.label = label;
  }
}

export default class CustomSidebarComponent extends Component {

  @tracked items = [
    new CustomSidebarItem('home', 'Home'),
    new CustomSidebarItem('my-posts', 'My Posts'),
    ...
  ];
  // rest of your code
}

这可能比创建一堆普通对象更冗长,但我发现它更容易扩展和推理,特别是如果你需要做一些事情,比如将数据传递给嵌套组件。

4 个赞

您好,Alteras,

感谢您的建议!!!它确实按您建议的方法奏效了,让我们的工作轻松了不少 :grinning_face:

2 个赞

我认为,当你像 OP 中描述的那样跟踪一个数组时,你跟踪的是数组引用,而不是数组中单个对象的更改。

另一种处理方法是使用 trackedObject,我们在 Discourse 的多个地方都使用了它。

3 个赞

感谢您的意见。我还有其他后续问题:

使用像 immer 这样的库是否可行?
如果可行,我应该如何在 discourse 中包含 immer?
我曾看到过“将其从 node_modules 复制到 assets 中”的说法,但我更希望能够通过 npm 来实现,或者在 header 标签中提供一个 cdn 链接。

您可以使用 loadScript() 来加载 JS,即使提供的 URL 是外部 URL。使用 jsdelivr 等外部服务应该也可以。

import loadScript from "discourse/lib/load-script";

...

loadScript(URL).then(() => {
   // your code
})

虽然我没有广泛测试过,但动态导入也应该可以工作。

async function load() {
  const {
    default: myDefault,
    foo,
    bar,
  } = await import(URL);
  // rest of code
}

我应该指出,将 NPM 文件直接保存在您的代码中可能比依赖外部 CDN 更安全,具体取决于 JS 的关键程度。同样,您可能需要在浏览器中使用它之前进行一些打包和预处理。


至于 Immer 是否能与 discourse 一起使用,我不知道。

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.