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="@index" の代わりに key="id"

質問:

  • @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'),
    ...
  ];
  // 残りのコード
}

プレーンなオブジェクトを多数作成するよりも冗長になる可能性がありますが、特にネストされたコンポーネントにデータを渡すようなことを行う必要がある場合、拡張や推論が容易になると考えています。

「いいね!」 4

Alteras様

ご提案ありがとうございます。ご提案いただいたアプローチでうまくいき、本当に助かりました。:grinning_face:

「いいね!」 2

OPで説明されているように配列を追跡する場合、配列参照を追跡しており、配列内の個々のオブジェクトへの変更を追跡していないと考えています。

別の処理方法として trackedObject を使用する方法があります。これは Discourse のいくつかの場所で使用しています。

「いいね!」 3

ご協力ありがとうございます。さらにいくつか質問があります。

immerのようなライブラリを使用することは可能でしょうか?
もし可能であれば、discourseにimmerをどのように含めるべきでしょうか?
「node_modulesからassetsにコピーする」という言及を見ましたが、npm経由で、あるいはヘッダータグにCDNリンクを含める方法があればそちらを希望します。

loadScript() を使用すると、URL が外部 URL であっても JS を読み込むことができます。 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.