Otra forma de rastrear estructuras de datos anidadas

El estado @tracked de un array u objeto dentro de un componente de Glimmer no activa actualizaciones cuando cambias un miembro del array/objeto.

Este tema habla sobre algunas opciones para que tu vista se actualice correctamente. Quería añadir otra forma, ya que las opciones que se comentan allí no me funcionaron.

Cómo lo resolví es: en lugar de asignar un elemento a la vez, asigna el array completo de una vez. La misma estrategia funcionaría con un objeto o cualquier otra estructura de datos.

En un componente de 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 ES LA LÍNEA A NOTAR
  // Debido a que usamos = directamente en el array, esto activará una actualización.
  // Mientras que añadir al array o establecer elementos por índice no activaría una actualización.
  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);
  }
}

Podrías hacer esto también con una actualización parcial. Lo importante es que JavaScript detecte que se está asignando un valor diferente. Así que, aunque .concat funcionará, porque genera un nuevo array distinto del anterior:

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

Probablemente .push no funcionará, porque el array resultante es el mismo ID de objeto que el original, por lo tanto, JavaScript piensa que nada ha cambiado.

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

Si estás trabajando con un objeto, una forma fácil de generar uno nuevo es con Object.assign() - que crea una nueva estructura de datos extendida, como .concat arriba.

Esto ha sido así durante mucho tiempo para otros frameworks como Vue.js también; puedes encontrar temas sobre esto que se remontan a 10 años. Tiene que ver con la forma en que se implementa en JavaScript, probablemente con getters/setters en el objeto padre o con la API observe. Pero si se implementa con la API observe, entonces hay un poco menos de excusa para este comportamiento, ya que sería fácil activar el flag subtree… Quizás no lo eligen por preocupaciones de rendimiento al observar una estructura profundamente anidada. Pero me desvío, estos son detalles de implementación de las bibliotecas subyacentes. La solución alternativa anterior debería ayudarte a navegar esto.

2 Me gusta

Me pregunto si TrackedArray ayudaría aquí?
No profundicé más en eso. Solo recordé que el código de Discourse lo usa aquí y allá.

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

2 Me gusta

Imagina si esto estuviera documentado. :laughing:

Pero sí, eso parece diseñado precisamente para este escenario.

2 Me gusta