No capítulo sobre como projetar a forma do estado , os documentos sugerem manter seu estado em um objeto codificado por ID:
Mantenha cada entidade em um objeto armazenado com um ID como uma chave e use IDs para referenciá-lo a partir de outras entidades ou listas.
Eles continuam a afirmar
Pense no estado do aplicativo como um banco de dados.
Estou trabalhando na forma de estado para uma lista de filtros, alguns dos quais serão abertos (são exibidos em um pop-up) ou têm opções selecionadas. Quando li "Pense no estado do aplicativo como um banco de dados", pensei em pensar neles como uma resposta JSON, pois seria retornada de uma API (ela própria apoiada por um banco de dados).
Então eu estava pensando nisso 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',
}]
No entanto, os documentos sugerem um formato mais parecido com
{
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',
}
}
Em teoria, isso não deve importar, desde que os dados sejam serializáveis (sob o título "Estado") .
Então, fui feliz com a abordagem de matriz de objetos, até que estava escrevendo meu redutor.
Com a abordagem object-keyed-by-id (e uso liberal da sintaxe de propagação), a OPEN_FILTER
parte do redutor torna-se
switch (action.type) {
case OPEN_FILTER: {
return { ...state, { ...state[action.id], open: true } }
}
Considerando que, com a abordagem de matriz de objetos, é mais prolixo (e dependente da função 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));
}
...
Portanto, minhas perguntas são três:
1) A simplicidade do redutor é a motivação para seguir a abordagem de objeto-chave-por-id? Existem outras vantagens nessa forma de estado?
e
2) Parece que a abordagem objeto-chave-por-id torna mais difícil lidar com entrada / saída JSON padrão para uma API. (É por isso que optei pela matriz de objetos em primeiro lugar.) Então, se você seguir essa abordagem, você apenas usa uma função para transformá-la entre o formato JSON e o formato de estado? Isso parece desajeitado. (Embora, se você defender essa abordagem, parte do seu raciocínio seja menos desajeitado do que o redutor de matriz de objetos acima?)
e
3) Eu sei que Dan Abramov projetou redux para ser teoricamente agnóstico de estrutura de dados de estado (como sugerido por "Por convenção, o estado de nível superior é um objeto ou alguma outra coleção de valor-chave como um Mapa, mas tecnicamente pode ser qualquer tipo , " ênfase minha). Mas, dado o exposto acima, é apenas "recomendado" mantê-lo como um objeto codificado por ID, ou há outros pontos de dor imprevistos que vou encontrar usando uma matriz de objetos que o tornam tal que eu deveria simplesmente abortar isso planeja e tenta ficar com um objeto codificado por ID?
fonte
sort_by
?const sorted = _.sortBy(collection, 'attribute');
Respostas:
Q1: A simplicidade do redutor é o resultado de não ter que pesquisar na matriz para encontrar a entrada correta. Não ter que pesquisar na matriz é a vantagem. Os seletores e outros acessadores de dados podem acessar esses itens e geralmente acessam
id
. Ter que pesquisar na matriz para cada acesso se torna um problema de desempenho. Quando seus arrays ficam maiores, o problema de desempenho piora drasticamente. Além disso, à medida que seu aplicativo se torna mais complexo, mostrando e filtrando dados em mais lugares, o problema também piora. A combinação pode ser prejudicial. Acessando os itens porid
, o tempo de acesso muda deO(n)
paraO(1)
, o que para grandesn
(aqui itens de array) faz uma grande diferença.P2: Você pode usar
normalizr
para ajudá-lo com a conversão de API para loja. A partir do normalizr V3.1.0, você pode usar desnormalizar para fazer o contrário. Dito isso, os aplicativos costumam ser mais consumidores do que produtores de dados e, como tal, a conversão para armazenamento geralmente é feita com mais frequência.P3: Os problemas que você encontrará ao usar um array não são tanto problemas com a convenção de armazenamento e / ou incompatibilidades, mas mais problemas de desempenho.
fonte
Essa é a ideia principal.
1) Ter objetos com IDs únicos permite que você sempre use aquele id ao fazer referência ao objeto, então você tem que passar a quantidade mínima de dados entre ações e redutores. É mais eficiente do que usar array.find (...). Se você usar a abordagem de array, terá que passar o objeto inteiro e isso pode ficar confuso muito em breve, você pode acabar recriando o objeto em diferentes redutores, ações ou mesmo no contêiner (você não quer isso). As visualizações sempre serão capazes de obter o objeto completo, mesmo que seu redutor associado contenha apenas o ID, porque ao mapear o estado você obterá a coleção em algum lugar (a visualização obtém todo o estado para mapeá-lo para as propriedades). Por tudo o que eu disse, as ações acabam tendo a quantidade mínima de parâmetros e reduzem a quantidade mínima de informações, experimente,
2) A conexão com a API não deve afetar a arquitetura de seu storage e redutores, por isso você tem ações, para manter a separação de preocupações. Basta colocar sua lógica de conversão dentro e fora da API em um módulo reutilizável, importar esse módulo nas ações que usam a API, e pronto.
3) Usei arrays para estruturas com IDs, e essas são as consequências imprevistas que sofri:
Acabei mudando minha estrutura de dados e reescrevendo muito código. Você foi avisado, por favor, não se meta em problemas.
Além disso:
4) A maioria das coleções com IDs são feitas para usar o ID como uma referência para todo o objeto, você deve tirar vantagem disso. As chamadas de API obterão o ID e o restante dos parâmetros, assim como suas ações e redutores.
fonte
O principal motivo pelo qual você deseja manter entidades em objetos armazenados com IDs como chaves (também chamados de normalizados ), é que é realmente complicado trabalhar com objetos profundamente aninhados (que é o que você normalmente obtém de APIs REST em um aplicativo mais complexo) - tanto para seus componentes quanto para seus redutores.
É um pouco difícil ilustrar os benefícios de um estado normalizado com seu exemplo atual (já que você não tem uma estrutura profundamente aninhada ). Mas digamos que as opções (no seu exemplo) também tenham um título e foram criadas por usuários em seu sistema. Isso faria com que a resposta fosse mais ou menos assim:
Agora, digamos que você queira criar um componente que mostra uma lista de todos os usuários que criaram opções. Para fazer isso, primeiro você teria que solicitar todos os itens, em seguida, iterar sobre cada uma de suas opções e, por último, obter o created_by.username.
Uma solução melhor seria normalizar a resposta em:
Com essa estrutura, é muito mais fácil e mais eficiente listar todos os usuários que criaram opções (nós os temos isolados em entity.optionCreators, então só temos que percorrer essa lista).
Também é bastante simples mostrar, por exemplo, os nomes de usuário daqueles que criaram opções para o item de filtro com ID 1:
Uma resposta JSON pode ser normalizada usando, por exemplo, normalizr .
Provavelmente é uma recomendação para aplicativos mais complexos com muitas respostas de API profundamente aninhadas. Porém, em seu exemplo particular, isso realmente não importa muito.
fonte
map
retorna indefinido como aqui , se os recursos forem buscados separadamente, tornandofilter
o processo muito complicado. Há uma solução?