v19.2Latest

Escolhendo a Estrutura do Estado

Estruturar bem o estado pode fazer a diferença entre um componente que é agradável de modificar e depurar e um que é uma fonte constante de bugs. Aqui estão algumas dicas que você deve considerar ao estruturar o estado.

Você aprenderá
  • Quando usar uma única variável de estado versus múltiplas
  • O que evitar ao organizar o estado
  • Como corrigir problemas comuns com a estrutura do estado

Princípios para estruturar o estado

Quando você escreve um componente que mantém algum estado, terá que fazer escolhas sobre quantas variáveis de estado usar e qual deve ser a forma dos seus dados. Embora seja possível escrever programas corretos mesmo com uma estrutura de estado subótima, existem alguns princípios que podem orientá-lo a fazer melhores escolhas:

  1. Agrupe estados relacionados.Se você sempre atualiza duas ou mais variáveis de estado ao mesmo tempo, considere uni-las em uma única variável de estado.
  2. Evite contradições no estado.Quando o estado é estruturado de forma que várias partes dele possam se contradizer e "discordar" umas das outras, você abre espaço para erros. Tente evitar isso.
  3. Evite estado redundante.Se você pode calcular alguma informação a partir das props do componente ou de suas variáveis de estado existentes durante a renderização, você não deve colocar essa informação no estado desse componente.
  4. Evite duplicação no estado.Quando os mesmos dados são duplicados entre múltiplas variáveis de estado ou dentro de objetos aninhados, é difícil mantê-los sincronizados. Reduza a duplicação quando possível.
  5. Evite estado profundamente aninhado.Estado com hierarquia profunda não é muito conveniente para atualizar. Quando possível, prefira estruturar o estado de forma plana.

O objetivo por trás desses princípios étornar o estado fácil de atualizar sem introduzir erros. Remover dados redundantes e duplicados do estado ajuda a garantir que todas as suas partes permaneçam sincronizadas. Isso é semelhante a como um engenheiro de banco de dados pode querer"normalizar" a estrutura do banco de dadospara reduzir a chance de bugs. Parafraseando Albert Einstein,"Torne seu estado o mais simples possível — mas não mais simples."

Agora vamos ver como esses princípios se aplicam na prática.

Às vezes você pode ficar em dúvida entre usar uma única ou múltiplas variáveis de estado.

Você deveria fazer isso?

Ou isso?

Tecnicamente, você pode usar qualquer uma dessas abordagens. Masse duas variáveis de estado sempre mudam juntas, pode ser uma boa ideia unificá-las em uma única variável de estado.Então você não esquecerá de sempre mantê-las sincronizadas, como neste exemplo onde mover o cursor atualiza ambas as coordenadas do ponto vermelho:

Outro caso em que você agrupará dados em um objeto ou array é quando você não sabe quantas partes de estado precisará. Por exemplo, é útil quando você tem um formulário onde o usuário pode adicionar campos personalizados.

Armadilha

Se sua variável de estado for um objeto, lembre-se de quevocê não pode atualizar apenas um campo nelesem copiar explicitamente os outros campos. Por exemplo, você não pode fazersetPosition({ x: 100 })no exemplo acima porque ele não teria a propriedadey! Em vez disso, se você quisesse definir apenasx, você fariasetPosition({ ...position, x: 100 }), ou as dividiria em duas variáveis de estado e fariasetX(100).

Evite contradições no estado

Aqui está um formulário de feedback de hotel com as variáveis de estadoisSending e isSent:

Embora este código funcione, ele deixa a porta aberta para estados "impossíveis". Por exemplo, se você esquecer de chamarsetIsSent e setIsSendingjuntos, você pode acabar em uma situação onde tantoisSendingquantoisSentsãotrueao mesmo tempo. Quanto mais complexo seu componente for, mais difícil será entender o que aconteceu.

ComoisSending e isSentnunca devem sertrueao mesmo tempo, é melhor substituí-los por uma única variável de estadostatusque pode assumir um detrêsestados válidos:'typing'(inicial),'sending', e'sent':

Você ainda pode declarar algumas constantes para legibilidade:

Mas elas não são variáveis de estado, então você não precisa se preocupar com elas ficarem dessincronizadas uma com a outra.

Evite estado redundante

Se você pode calcular alguma informação a partir das props do componente ou de suas variáveis de estado existentes durante a renderização, vocênão devecolocar essa informação no estado desse componente.

Por exemplo, veja este formulário. Ele funciona, mas você consegue encontrar algum estado redundante nele?

Este formulário tem três variáveis de estado:firstName,lastName, efullName. No entanto,fullNameé redundante.Você sempre pode calcularfullNamea partir defirstName e lastNamedurante a renderização, então remova-o do estado.

É assim que você pode fazer isso:

Aqui,fullName não éuma variável de estado. Em vez disso, é calculado durante a renderização:

Como resultado, os manipuladores de alteração não precisam fazer nada especial para atualizá-lo. Quando você chamasetFirstNameousetLastName, você dispara uma re-renderização, e então o próximofullNameserá calculado a partir dos dados mais recentes.

Deep Dive
Não espelhe props no estado

Evite duplicação no estado

Este componente de lista de menu permite que você escolha um único lanche de viagem entre vários:

Atualmente, ele armazena o item selecionado como um objeto na variável de estadoselectedItem. No entanto, isso não é ideal:o conteúdo deselectedItemé o mesmo objeto que um dos itens dentro da listaitems.Isso significa que a informação sobre o próprio item está duplicada em dois lugares.

Por que isso é um problema? Vamos tornar cada item editável:

Observe como, se você primeiro clicar em "Escolher" em um item edepoiseditá-lo,a entrada é atualizada, mas o rótulo na parte inferior não reflete as edições.Isso acontece porque você duplicou o estado e esqueceu de atualizarselectedItem.

Embora você pudesse atualizarselectedItemtambém, uma correção mais fácil é remover a duplicação. Neste exemplo, em vez de um objetoselectedItem(que cria uma duplicação com objetos dentro deitems), você mantém oselectedIdno estado edepoisobtém oselectedItempesquisando no arrayitemspor um item com esse ID:

O estado costumava ser duplicado assim:

  • items = [{ id: 0, title: 'pretzels'}, ...]
  • selectedItem = {id: 0, title: 'pretzels'}

Mas após a mudança, ficou assim:

  • items = [{ id: 0, title: 'pretzels'}, ...]
  • selectedId = 0

A duplicação desapareceu e você mantém apenas o estado essencial!

Agora, se você editar o itemselecionado, a mensagem abaixo será atualizada imediatamente. Isso ocorre porquesetItemsdispara uma nova renderização eitems.find(...)encontraria o item com o título atualizado. Você não precisava armazenaro item selecionadono estado, porque apenas oID selecionadoé essencial. O resto poderia ser calculado durante a renderização.

Evite estado profundamente aninhado

Imagine um plano de viagem composto por planetas, continentes e países. Você pode ficar tentado a estruturar seu estado usando objetos e arrays aninhados, como neste exemplo:

Agora, digamos que você queira adicionar um botão para excluir um lugar que já visitou. Como você faria isso?Atualizar estado aninhadoenvolve fazer cópias dos objetos por toda a cadeia, a partir da parte que mudou. Excluir um lugar profundamente aninhado envolveria copiar toda a cadeia de lugares pai. Esse tipo de código pode ser muito verboso.

Se o estado estiver muito aninhado para ser atualizado facilmente, considere torná-lo "plano".Aqui está uma maneira de reestruturar esses dados. Em vez de uma estrutura em árvore onde cadaplacetem um array deseus lugares filhos, você pode fazer com que cada lugar mantenha um array deIDs dos seus lugares filhos. Em seguida, armazene um mapeamento de cada ID de lugar para o lugar correspondente.

Essa reestruturação de dados pode lembrá-lo de ver uma tabela de banco de dados:

Agora que o estado está “achatado” (também conhecido como “normalizado”), atualizar itens aninhados se torna mais fácil.

Para remover um local agora, você só precisa atualizar dois níveis de estado:

  • A versão atualizada do seu localpaideve excluir o ID removido do seu arraychildIds.
  • A versão atualizada do objeto “tabela” raiz deve incluir a versão atualizada do local pai.

Aqui está um exemplo de como você poderia fazer isso:

Você pode aninhar o estado o quanto quiser, mas torná-lo "plano" pode resolver inúmeros problemas. Isso facilita a atualização do estado e ajuda a garantir que você não tenha duplicação em diferentes partes de um objeto aninhado.

Às vezes, você também pode reduzir o aninhamento do estado movendo parte do estado aninhado para os componentes filhos. Isso funciona bem para estado de UI efêmero que não precisa ser armazenado, como se um item está com o mouse sobre ele.

Recapitulação

  • Se duas variáveis de estado sempre atualizam juntas, considere uni-las em uma.
  • Escolha suas variáveis de estado com cuidado para evitar criar estados "impossíveis".
  • Estruture seu estado de forma a reduzir as chances de você cometer um erro ao atualizá-lo.
  • Evite estado redundante e duplicado para que você não precise mantê-lo sincronizado.
  • Não coloque propsemestado, a menos que você queira especificamente evitar atualizações.
  • Para padrões de UI como seleção, mantenha o ID ou índice no estado em vez do próprio objeto.
  • Se atualizar um estado profundamente aninhado for complicado, tente torná-lo plano.

Try out some challenges

Challenge 1 of 4:Fix a component that’s not updating #

This Clock component receives two props: color and time. When you select a different color in the select box, the Clock component receives a different color prop from its parent component. However, for some reason, the displayed color doesn’t update. Why? Fix the problem.