Outra forma de rastrear estruturas de dados aninhadas

O estado @tracked de um array ou objeto dentro de um componente glimmer não aciona atualizações quando você altera um membro do array/objeto.

Este tópico fala sobre algumas opções para fazer sua visualização atualizar corretamente. Eu queria adicionar outra maneira, pois as opções discutidas lá não funcionaram para mim.

Como resolvi isso: Em vez de atribuir um elemento por vez, atribua o array inteiro de uma vez. Essa mesma estratégia funcionaria com um objeto ou qualquer outra estrutura de dados.

Em um componente 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
  })

  // ESTA É A LINHA PARA NOTAR
  // Como usamos = diretamente no array, isso acionará uma atualização.
  // Enquanto adicionar ao array ou definir elementos por índice não acionaria uma atualização.
  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);
  }
}

Você também poderia fazer isso com uma atualização parcial. O importante é que o Javascript detecte que um valor diferente está sendo atribuído. Portanto, embora .concat funcione, porque ele gera um novo array distinto do antigo:

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

Usar .push provavelmente não funcionará, porque o array resultante é o mesmo ID de objeto do original, portanto, o Javascript pensa que nada mudou.

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

Se você estiver trabalhando com um objeto, uma maneira fácil de gerar um novo é com Object.assign() - que cria uma nova estrutura de dados estendida, como .concat acima.

Isso tem sido o caso há muito tempo para outros frameworks como Vue.js também - você pode encontrar tópicos sobre isso datando de 10 anos atrás. Tem a ver com a forma como está sendo implementado em Javascript, provavelmente getters/setters no objeto pai ou a API observe. Mas se for implementado com a API observe, então há um pouco menos de desculpa para esse comportamento, já que seria fácil alternar o sinalizador subtree… Talvez eles escolham não fazer isso por preocupações de desempenho ao observar uma estrutura profundamente aninhada? Mas divago, esses são detalhes de implementação das bibliotecas subjacentes. A solução alternativa acima deve ajudá-lo a navegar por isso.

2 curtidas

Eu me pergunto se TrackedArray ajudaria aqui?
Eu não me aprofundei mais do que isso. Apenas me lembrei que o código do Discourse usa isso aqui e ali.

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

2 curtidas

Imagine se isso fosse documentado. :laughing:

Mas sim, isso parece projetado precisamente para este cenário.

2 curtidas