En el capítulo sobre Diseño de la forma del estado , los documentos sugieren mantener su estado en un objeto codificado por ID:
Mantenga todas las entidades de un objeto almacenadas con un ID como clave y utilice ID para hacer referencia a ellas desde otras entidades o listas.
Continúan diciendo
Piense en el estado de la aplicación como una base de datos.
Estoy trabajando en la forma del estado para obtener una lista de filtros, algunos de los cuales estarán abiertos (se muestran en una ventana emergente) o tienen opciones seleccionadas. Cuando leí "Piense en el estado de la aplicación como una base de datos", pensé en pensar en ellos como una respuesta JSON, ya que se devolvería desde una API (respaldada por una base de datos).
Así que estaba pensando en ello como
[{
id: '1',
name: 'View',
open: false,
options: ['10', '11', '12', '13'],
selectedOption: ['10'],
parent: null,
},
{
id: '10',
name: 'Time & Fees',
open: false,
options: ['20', '21', '22', '23', '24'],
selectedOption: null,
parent: '1',
}]
Sin embargo, los documentos sugieren un formato más parecido a
{
1: {
name: 'View',
open: false,
options: ['10', '11', '12', '13'],
selectedOption: ['10'],
parent: null,
},
10: {
name: 'Time & Fees',
open: false,
options: ['20', '21', '22', '23', '24'],
selectedOption: null,
parent: '1',
}
}
En teoría, no debería importar siempre que los datos sean serializables (bajo el título "Estado") .
Así que fui felizmente con el enfoque de matriz de objetos, hasta que escribí mi reductor.
Con el enfoque de objeto codificado por id (y el uso liberal de la sintaxis de propagación), la OPEN_FILTER
parte del reductor se convierte en
switch (action.type) {
case OPEN_FILTER: {
return { ...state, { ...state[action.id], open: true } }
}
Mientras que con el enfoque de matriz de objetos, es más detallado (y dependiente de la función auxiliar)
switch (action.type) {
case OPEN_FILTER: {
// relies on getFilterById helper function
const filter = getFilterById(state, action.id);
const index = state.indexOf(filter);
return state
.slice(0, index)
.concat([{ ...filter, open: true }])
.concat(state.slice(index + 1));
}
...
Entonces mis preguntas son triples:
1) ¿Es la simplicidad del reductor la motivación para optar por el enfoque de objeto codificado por id? ¿Hay otras ventajas en esa forma de estado?
y
2) Parece que el enfoque de objeto con clave por id hace que sea más difícil lidiar con la entrada / salida JSON estándar para una API. (Es por eso que me decidí por la matriz de objetos en primer lugar). Entonces, si sigue ese enfoque, ¿usa una función para transformarlo entre el formato JSON y el formato de forma de estado? Eso parece torpe. (Aunque si defiende ese enfoque, ¿es parte de su razonamiento que eso es menos torpe que el reductor de matriz de objetos anterior?)
y
3) Sé que Dan Abramov diseñó redux para ser teóricamente agnóstico de estructura de datos de estado (como sugiere "Por convención, el estado de nivel superior es un objeto o alguna otra colección de valores clave como un mapa, pero técnicamente puede ser cualquier tipo , " énfasis mío). Pero dado lo anterior, ¿es simplemente "recomendado" mantenerlo como un objeto codificado por ID, o hay otros puntos de dolor imprevistos con los que me voy a encontrar al usar una matriz de objetos que hacen que deba abortar eso? ¿Planea e intenta ceñirse a un objeto codificado por ID?
fuente
sort_by
?const sorted = _.sortBy(collection, 'attribute');
Respuestas:
P1: La simplicidad del reductor es el resultado de no tener que buscar en la matriz para encontrar la entrada correcta. La ventaja es no tener que buscar en la matriz. Los selectores y otros proveedores de acceso a datos pueden acceder, ya menudo lo hacen, a estos elementos mediante
id
. Tener que buscar en la matriz para cada acceso se convierte en un problema de rendimiento. Cuando sus matrices aumentan de tamaño, el problema de rendimiento empeora abruptamente. Además, a medida que su aplicación se vuelve más compleja y muestra y filtra datos en más lugares, el problema también empeora. La combinación puede resultar perjudicial. Al acceder a los elementos porid
, el tiempo de acceso cambia deO(n)
aO(1)
, lo que para los elementos grandesn
(en este caso, los elementos de matriz) marca una gran diferencia.P2: Puede usarlo
normalizr
para ayudarlo con la conversión de API a tienda. A partir de normalizr V3.1.0 puede usar desnormalize para ir al revés. Dicho esto, las aplicaciones suelen ser más consumidores que productores de datos y, como tal, la conversión a la tienda suele realizarse con más frecuencia.P3: Los problemas con los que se encontrará al usar una matriz no son tanto problemas con la convención de almacenamiento y / o incompatibilidades, sino más problemas de rendimiento.
fuente
Esa es la idea clave.
1) Tener objetos con ID únicos le permite utilizar siempre ese ID al hacer referencia al objeto, por lo que debe pasar la cantidad mínima de datos entre acciones y reductores. Es más eficiente que usar array.find (...). Si usa el enfoque de matriz, debe pasar todo el objeto y eso puede complicarse muy pronto, puede terminar recreando el objeto en diferentes reductores, acciones o incluso en el contenedor (no quiere eso). Las vistas siempre podrán obtener el objeto completo incluso si su reductor asociado solo contiene la ID, porque al mapear el estado, obtendrá la colección en alguna parte (la vista obtiene el estado completo para asignarlo a las propiedades). Por todo lo que he dicho, las acciones terminan teniendo la mínima cantidad de parámetros y reducen la mínima cantidad de información, pruébalo,
2) La conexión a la API no debe afectar la arquitectura de tu almacenamiento y reductores, por eso tienes acciones, para mantener la separación de preocupaciones. Simplemente coloque su lógica de conversión dentro y fuera de la API en un módulo reutilizable, importe ese módulo en las acciones que usan la API, y eso debería ser todo.
3) Usé matrices para estructuras con ID, y estas son las consecuencias imprevistas que he sufrido:
Terminé cambiando mi estructura de datos y reescribiendo mucho código. Se le ha advertido, no se meta en problemas.
También:
4) La mayoría de las colecciones con ID están destinadas a usar el ID como referencia al objeto completo, debería aprovechar eso. Las llamadas a la API obtendrán el ID y luego el resto de los parámetros, al igual que sus acciones y reductores.
fuente
La razón principal por la que desea mantener las entidades en objetos almacenados con ID como claves (también llamado normalizado ) es que es realmente engorroso trabajar con objetos profundamente anidados (que es lo que normalmente obtiene de las API REST en una aplicación más compleja). tanto para sus componentes como para sus reductores.
Es un poco difícil ilustrar los beneficios de un estado normalizado con su ejemplo actual (ya que no tiene una estructura profundamente anidada ). Pero digamos que las opciones (en su ejemplo) también tenían un título y fueron creadas por usuarios en su sistema. Eso haría que la respuesta se viera así:
Ahora digamos que desea crear un componente que muestre una lista de todos los usuarios que han creado opciones. Para hacer eso, primero tendría que solicitar todos los elementos, luego iterar sobre cada una de sus opciones y, por último, obtener el created_by.username.
Una mejor solución sería normalizar la respuesta en:
Con esta estructura, es mucho más fácil y eficiente listar todos los usuarios que han creado opciones (los tenemos aislados en entity.optionCreators, así que solo tenemos que recorrer esa lista).
También es bastante sencillo mostrar, por ejemplo, los nombres de usuario de aquellos que han creado opciones para el elemento de filtro con ID 1:
Una respuesta JSON se puede normalizar usando, por ejemplo, normalizr .
Probablemente sea una recomendación para aplicaciones más complejas con muchas respuestas API profundamente anidadas. Sin embargo, en su ejemplo particular, realmente no importa mucho.
fuente
map
devuelve indefinido como aquí , si los recursos se obtienen por separado, lo que hace quefilter
s sea demasiado complicado. ¿Existe una solución?