중첩 데이터 구조를 추적하는 또 다른 방법

glimmer 컴포넌트 내에서 배열이나 객체의 @tracked 상태는 배열/객체의 멤버를 변경할 때 업데이트를 트리거하지 않습니다.

이 주제에서는 뷰가 올바르게 업데이트되도록 하는 몇 가지 옵션에 대해 다루고 있습니다. 저는 거기에 언급된 옵션들이 제게 작동하지 않아서, 또 다른 방법을 추가하고 싶었습니다.

제가 이 문제를 해결한 방법은 다음과 같습니다. 하나씩 할당하는 대신, 배열 전체를 한 번에 할당하는 것입니다. 이 전략은 객체나 기타 데이터 구조에도 동일하게 적용됩니다.

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
  })

  // 주목해야 할 라인입니다.
  // 배열에 직접 =를 사용하므로 업데이트가 트리거됩니다.
  // 반면 배열에 push를 하거나 인덱스로 요소를 설정하는 것은 업데이트를 트리거하지 않습니다.
  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);
  }
}

부분 업데이트(partial update)로도 이 방법을 사용할 수 있습니다. 중요한 것은 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로 구현되었다면, 서브트리 플래그를 토글하는 것이 쉽기 때문에 이 동작에 대한 변명이 조금은 약해집니다… 아마도 깊게 중첩된 구조를 관찰할 때의 성능 문제를 우려하여 의도적으로 그렇게 하지 않는 것일까요? 하지만 이건 좀 더 들어가면, 이는 기초 라이브러리의 구현 세부 사항입니다. 위의 우회 방법은 이 문제를 해결하는 데 도움이 될 것입니다.

여기서 TrackedArray가 도움이 될지 궁금합니다.
그 이상 깊이 파고들지 않았습니다. 다만 Discourse 코드에서 이걸 여기저기 사용한다는 것을 기억할 뿐입니다.

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

이게 문서화되어 있다면 어떨지 상상해 보세요. :laughing:

하지만 네, 이 기능은 정확히 이 시나리오를 위해 설계된 것 같습니다.