Изменение отслеживания компонентов Glimmer не работает: мутация свойств в массиве @tracked

Всем привет!

У меня возникли проблемы с обнаружением изменений в компоненте Glimmer, и я был бы признателен за помощь. При изменении свойств объектов внутри массива, помеченного как @tracked, шаблон не перерисовывается.

Моя конфигурация:

```javascript

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

    }

    // ... больше элементов

  ];



  @action

  toggleExpanded(item) {

    item.expanded = !item.expanded; // Это изменение не вызывает перерисовку

    console.log('Переключено:', 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 для неизменяемых обновлений — хотел попробовать, но не могу настроить импорты npm в темах Discourse

  3. Разные стратегии ключейkey="id" вместо key="@index"

Вопросы:

  • Должно ли изменение свойств объектов внутри массивов, помеченных как @tracked, работать в Glimmer?

  • Нужно ли каким-то образом помечать отдельные объекты как отслеживаемые?

  • Есть ли рекомендуемый паттерн для этого случая?

  • Может ли это быть связано с запуском в среде темы Discourse?

  • Существуют ли ограничения импортов npm в темах Discourse, влияющие на реактивные библиотеки?

Окружение:

  • Компонент темы Discourse

  • Компоненты Glimmer с файлами .gjs

  • Последняя версия Discourse — обновлена сегодня

Буду очень признателен за любые подсказки! Не упускаю ли я что-то фундаментальное в системе реактивности Glimmer?

Спасибо!

Хм, это странно. Обычно трюк с присваиванием массива работает.

Альтернатива, которую я нашел, — сделать объекты в массиве экземплярами класса с собственными отслеживаемыми свойствами. Например:

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'),
    ...
  ];
  // остальной код
}

Это может быть более многословно, чем создание множества простых объектов, но, как я заметил, так проще расширять функционал и понимать логику, особенно если нужно передавать данные во вложенные компоненты.

Привет, Alteras,

Спасибо за ваше предложение !!! Ваш подход сработал, как вы и советовали, и действительно немного облегчил нам день ::)** :grinning_face:

Я полагаю, что когда вы отслеживаете массив, как описано в первом сообщении, вы отслеживаете ссылку на массив, а не изменения отдельных объектов внутри него.

Другой способ решения — использовать trackedObject. Мы применяем его в нескольких местах в Discourse:

Спасибо за ваш вклад. У меня есть ещё один уточняющий вопрос:

Сработает ли использование библиотеки вроде Immer?
Если да, то как следует подключить Immer в Discourse?
Я видел упоминания о том, чтобы «скопировать её из node_modules в assets», но я бы предпочёл вариант с использованием npm или даже ссылки на CDN в теге header.

Вы можете использовать loadScript() для загрузки JS, даже если предоставленный URL является внешним. Использование внешних сервисов, таких как jsdelivr, должно работать с этим.

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

...

loadScript(URL).then(() => {
   // ваш код
})

Хотя я не тестировал это extensively, динамический импорт также должен работать.

async function load() {
  const {
    default: myDefault,
    foo,
    bar,
  } = await import(URL);
  // остальной код
}

Стоит отметить, что, вероятно, безопаснее сохранять файлы NPM напрямую в ваш код, вместо того чтобы полагаться на внешний CDN, в зависимости от того, насколько критичен JS. Аналогично, вам может потребоваться выполнить некоторую сборку и предварительную обработку, прежде чем использовать его в браузере.


Что касается того, будет ли Immer работать с Discourse, я не знаю.