Por que o Context não é uma ferramenta de "gerenciamento de estado"





TL; DR



Context e Redux são iguais?


Não. São ferramentas diferentes que fazem coisas diferentes e são usadas para finalidades diferentes.



O contexto é uma ferramenta para "gerenciamento de estado"?


Não. O contexto é uma forma de injeção de dependência. É um mecanismo de transporte que não controla nada. Qualquer "gerenciamento de estado" é feito manualmente, geralmente usando os ganchos useState () / useReducer ().



Context e useReducer () são uma substituição para Redux?


Não. Eles são um tanto semelhantes e parcialmente sobrepostos, mas diferem muito em termos de recursos.



Quando você deve usar o contexto?


Quando você deseja disponibilizar alguns dados para vários componentes, mas não deseja passar esses dados como suportes em todos os níveis da árvore de componentes.



Quando você deve usar Context e useReducer ()?


Quando você precisa gerenciar o estado de um componente moderadamente complexo em uma parte específica do seu aplicativo.



Quando você deve usar o Redux?


Redux é mais útil quando:



  • Grande número de componentes com estado usando os mesmos dados
  • O status do aplicativo é atualizado com frequência
  • Lógica complexa para atualizar o estado
  • O aplicativo tem uma base de código de médio a grande e muitas pessoas estão trabalhando nisso
  • Você quer saber quando, por que e como o estado do aplicativo é atualizado e ser capaz de visualizar essas mudanças
  • Você precisa de recursos mais poderosos para lidar com efeitos colaterais, estabilidade (persistência) e serialização de dados




Compreendendo o contexto e Redux



Para usar a ferramenta corretamente, é fundamental compreender:



  • Para que serve
  • Que tarefas ele resolve
  • Quando e por que foi criado


Também é importante entender quais problemas você está tentando resolver em seu aplicativo e usar as ferramentas que melhor os resolvem, não porque alguém lhe disse para usá-los, e não porque são populares, mas porque funcionam melhor para você em um situação particular.



A confusão em torno de Context e Redux se deve principalmente a um mal-entendido sobre a finalidade dessas ferramentas e quais tarefas elas resolvem. Portanto, antes de falar sobre quando devem ser usados, é preciso determinar o que são e quais os problemas que resolvem.



O que é contexto?



Vamos começar definindo o contexto da documentação oficial :



“O contexto permite que os dados sejam passados ​​pela árvore de componentes sem ter que passar props em níveis intermediários.



Em um aplicativo React típico, os dados são passados ​​de cima para baixo (de pai para filho) usando adereços. No entanto, esse método pode ser um exagero para alguns tipos de adereços (por exemplo, idioma selecionado, tema de interface) que precisam ser passados ​​para muitos componentes em um aplicativo. O contexto fornece uma maneira de distribuir esses dados entre os componentes sem ter que passar explicitamente os props em cada nível da árvore de componentes. "



Observe que esta definição não diz uma palavra sobre "gerenciamento", apenas sobre "transferência" e "distribuição".



A API de contexto atual (React.createContext ()) foi introduzida pela primeira vez no React 16.3 como uma substituição da API legada disponível nas versões anteriores do React, mas com várias falhas de design. Um dos principais problemas era que as atualizações dos valores passados ​​pelo contexto poderiam ser "bloqueadas" se o componente ignorasse a renderização por meio de shouldComponentUpdate (). Como muitos componentes recorreram a shouldComponentUpdate () para fins de otimização, a passagem de dados pelo contexto tornou-se inútil. createContext () foi projetado para resolver esse problema, portanto, qualquer atualização do valor será refletida nos componentes filhos, mesmo se o componente intermediário pular a renderização.



Usando contexto


O uso de contexto em um aplicativo pressupõe o seguinte:



  • Chame const MyContext = React.createContext () para instanciar o objeto de contexto
  • No componente pai, renderize & ltMyContext.Provider value = {someValue}>. Isso coloca alguns dados em contexto. Esses dados podem ser qualquer coisa: string, número, objeto, matriz, instância de classe, manipulador de eventos, etc.
  • Obtenha o valor de contexto em qualquer componente dentro do provedor chamando const theContextValue = useContext (MyContext)


Quando o componente pai é atualizado e a nova referência é passada como o valor do provedor, qualquer componente que "consome" o contexto será forçado a ser atualizado.



Normalmente, o valor do contexto é o estado do componente:



import { createContext } from 'react'

export const MyContext = createContext()

export function ParentComponent({ children }) {
  const [counter, setCounter] = useState(0)

  return (
    <MyContext.Provider value={[counter, setCounter]}>
      {children}
    </MyContext.Provider>
  )
}

      
      





O componente filho pode então chamar o gancho useContext () e ler o valor do contexto:



import { useContext } from 'react'
import { MyContext } from './MyContext'

export function NestedChildComponent() {
  const [counter, setCounter] = useContext(MyContext)

  // ...
}

      
      







Podemos ver que o contexto realmente não controla nada. Em vez disso, é uma espécie de cano. Você coloca dados no início (topo) do túnel usando <MyContext.Provider>, então esses dados são rolados para baixo até que o componente os solicite usando useContext (MyContext).



Assim, o objetivo principal do contexto é evitar a perfuração de escora. Em vez de passar dados como props em cada nível da árvore de componentes, qualquer componente aninhado em <MyContext.Provider> pode acessá-los por meio de useContext (MyContext). Isso elimina a necessidade de escrever código para implementar a lógica de passagem da propriedade.



Conceitualmente, esta é uma forma de injeção de dependência... Sabemos que a criança precisa de dados de um determinado tipo, mas ela não tenta criar ou definir esses dados sozinha. Em vez disso, ele depende de algum ancestral para passar esses dados em tempo de execução.



O que é Redux?



Aqui está o que a definição Redux Basics diz :



“Redux é um padrão de design e uma biblioteca para gerenciar e atualizar o estado do aplicativo usando eventos chamados operações. Redux atua como um repositório centralizado do estado do aplicativo, seguindo regras para garantir atualizações de estado previsíveis.



Redux permite que você gerencie o estado "global" - estado que é canalizado para várias partes do seu aplicativo.



Os padrões e ferramentas fornecidos pelo Redux tornam mais fácil determinar onde, quando, por que e como o estado foi atualizado e como o aplicativo respondeu a essa mudança. ”



Observe que esta descrição indica:



  • Gestão do estado
  • O objetivo do Redux é determinar por que e como uma mudança de estado ocorreu


Redux era originalmente uma implementação da "arquitetura Flux" , um padrão de design desenvolvido pelo Facebook em 2014, um ano após o lançamento do React. Desde o advento do Flux, a comunidade desenvolveu muitas bibliotecas que implementam esse conceito de diferentes maneiras. Redux surgiu em 2015 e rapidamente se tornou o vencedor deste concurso graças ao seu design pensado, resolvendo problemas comuns e excelente compatibilidade com o React.



Arquitetonicamente, Redux usa enfaticamente os princípios da programação funcional, o que permite escrever código na forma de "redutores" previsíveis, e separar a ideia de "o que aconteceu" da lógica que define "como o estado é atualizado quando este evento ocorre. " Redux também usa middleware como uma forma de estender os recursos da loja, incluindo o tratamento de efeitos colaterais .



Redux também fornece ferramentas de desenvolvedor para explorar o histórico de operações e mudanças de estado ao longo do tempo.



Redux e React


O próprio Redux é independente da IU - você pode usá-lo com qualquer camada de visualização (React, Vue, Angular, vanilla JS, etc.) ou nenhuma IU.



Na maioria das vezes, no entanto, Redux é usado em conjunto com React. A biblioteca React Redux é a camada de vinculação oficial da IU que permite que os componentes React interajam com o armazenamento Redux recuperando valores do estado Redux e iniciando operações. O React-Redux usa o contexto internamente. No entanto, deve-se notar que o React-Redux passa pelo contexto uma instância de armazenamento Redux, não o valor do estado atual!Este é um exemplo de uso de contexto para injeção de dependência. Sabemos que nossos componentes conectados ao Redux precisam interagir com a loja Redux, mas não sabemos ou não nos importamos com o que é essa loja quando definimos o componente. O armazenamento Redux real é injetado na árvore em tempo de execução usando o componente <Provider> fornecido pelo React-Redux.



Portanto, o React-Redux também pode ser usado para evitar "perfuração" (devido ao uso interno do contexto). Em vez de passar explicitamente o novo valor por meio de <MyContext.Provider>, podemos colocar esses dados no armazenamento Redux e depois recuperá-los no componente desejado.



Finalidade e casos de uso de (React-) Redux


O principal objetivo do Redux de acordo com a documentação oficial:



"Os padrões e ferramentas fornecidos pelo Redux tornam mais fácil entender quando, onde, por que e como ocorreu uma mudança de estado e como a aplicação reagiu a ela."



Existem vários outros motivos para usar o Redux. Um desses motivos é evitar a "perfuração".



Outros casos de uso:



  • Separação completa da lógica de gerenciamento de estado e da camada de IU
  • Distribuição da lógica de gerenciamento de estado entre diferentes camadas de IU (por exemplo, no processo de tradução de um aplicativo de AngularJS para React)
  • Usando middleware Redux para adicionar lógica extra ao inicializar operações
  • Capacidade de salvar partes do estado Redux
  • Capacidade de receber relatórios de bugs que podem ser reproduzidos por outros desenvolvedores
  • Capacidade de depurar rapidamente a lógica e a IU durante o desenvolvimento


Dan Abramov listou esses casos em seu artigo de 2016 Por que você não precisa de redux .



Por que o contexto não é uma ferramenta para "gerenciamento de estado"?



Estado são quaisquer dados que descrevam o comportamento de um aplicativo . Podemos categorizar o estado em categorias como estado do servidor, estado de comunicação e estado local, se quisermos, mas o aspecto principal é armazenar, ler, atualizar e usar dados.



David Khourshid, autor da biblioteca XState e especialista em gerenciamento de estado, observou em um de seus tweets que:



"Gerenciamento de estado trata de mudar o estado ao longo do tempo".



Assim, podemos dizer que "gestão do estado" significa o seguinte:



  • Armazenando o valor inicial
  • Obtendo o valor atual
  • Atualizando um valor


Além disso, geralmente há uma maneira de ser notificado quando o valor do estado atual for alterado.



Os ganchos do React useState () e useReducer () são ótimos exemplos de gerenciamento de estado. Com esses ganchos, podemos:



  • Armazene o valor inicial chamando um gancho
  • Obtenha o valor atual também chamando o gancho
  • Atualize o valor chamando setState () ou dispatch (), respectivamente
  • Esteja ciente das atualizações de estado ao renderizar novamente o componente


Redux e MobX também permitem que você gerencie o estado:



  • Redux armazena o valor inicial chamando o redutor raiz, permite ler o valor atual com store.getState (), atualizar o valor com store.dispatch (action) e receber notificações de atualização de estado via store.subscribe (listener)
  • MobX preserva um valor inicial atribuindo um valor a um campo de classe de armazenamento, permite que o valor atual seja lido e atualizado por meio de campos de armazenamento e recebe notificações de atualização de estado usando os métodos autorun () e computed ()


As ferramentas de gerenciamento de estado incluem até mesmo ferramentas de cache de servidor, como React-Query, SWR, Apollo e Urql - elas armazenam um valor inicial com base nos dados buscados, retornam o valor atual usando ganchos e permitem a atualização de valores por meio de "mutações de servidor" e notificam as alterações ao renderizar novamente o componente.



O React Context não atende aos critérios nomeados. Portanto, não é uma ferramenta de gerenciamento de estado


Conforme observado anteriormente, o próprio contexto não armazena nada. O componente pai, que renderiza <MyContext.Provider>, é responsável por passar o valor, que geralmente depende do estado do componente. O "gerenciamento de estado" real vem dos ganchos useState () / useReducer ().



David Khourshid também observa:



“Contexto é como o estado existente é compartilhado entre os componentes. O contexto não faz nada com o estado. "



E em um tweet posterior ,



"Acho que o contexto é como adereços ocultos naquele estado abstrato."



Qualquer coisa que o contexto faça é evitar a "perfuração".



Comparando Contexto e Redux



Vamos comparar as capacidades do contexto e React + Redux:



  • Contexto
    • Não armazena nada e não gerencia nada
    • Só funciona em componentes React
    • Passa abaixo um valor simples (único), que pode ser qualquer coisa (primitivo, objeto, classe, etc.)
    • Vamos ler este significado simples
    • Pode ser usado para evitar "perfuração"
    • Mostra o valor atual para os componentes Provedor e Consumidor nas ferramentas do desenvolvedor, mas não mostra o histórico de mudanças neste valor
    • Atualiza componentes de consumo quando o valor muda, mas não permite que a atualização seja ignorada
    • Não fornece nenhum mecanismo para lidar com os efeitos colaterais - apenas responsável pela renderização


  • React + Redux
    • Armazena e gerencia um valor simples (geralmente um objeto)
    • Funciona com qualquer IU e também fora dos componentes React
    • Vamos ler este significado simples
    • Pode ser usado para evitar "perfuração"
    • Pode atualizar o valor inicializando operações e executando redutores
    • Ferramentas de desenvolvedor mostram histórico de inicialização de operações e mudanças de estado
    • Oferece a capacidade de usar middleware para lidar com efeitos colaterais
    • Permite que os componentes se inscrevam para armazenar atualizações, recuperar partes específicas do estado da loja e controlar a re-renderização do componente




Obviamente, essas são ferramentas completamente diferentes com recursos diferentes. O único ponto de intersecção entre eles é para evitar a "perfuração".



Context and useReducer ()



Um dos problemas com a discussão "Contexto versus Redux" é que as pessoas geralmente querem dizer: "Eu usoReducer () para gerenciar o estado e o contexto para passar valor." Mas em vez disso, eles apenas dizem: "Estou usando o contexto." Esse, em minha opinião, é o principal motivo da confusão que contribui para a manutenção do mito de que o contexto "controla o Estado".



Considere a combinação de Context + useReducer (). Sim, esta combinação é muito semelhante a Redux + React-Redux. Ambas as combinações têm:



  • Valor armazenado
  • Função redutora
  • Capacidade de inicializar operações
  • A capacidade de passar um valor e lê-lo em componentes aninhados


No entanto, ainda existem algumas diferenças importantes entre eles, manifestadas em suas capacidades e comportamento. Observei essas diferenças em React, Redux e Context Behaviour e The (quase) Complete Guide to Rendering in React . Em resumo, o seguinte pode ser observado:



  • Context + useReducer () depende de passar o valor atual através do contexto. React-Redux passa a instância da loja Redux atual através do contexto
  • Isso significa que quando useReducer () produz um novo valor, todos os componentes inscritos no contexto são forçados a redesenhar, mesmo que usem apenas alguns dos dados. Isso pode levar a problemas de desempenho, dependendo do tamanho do valor do estado, do número de componentes assinados e da frequência de nova renderização. Com React-Redux, os componentes podem se inscrever em uma parte específica do valor da loja e apenas redesenhar quando essa parte muda


Existem outras diferenças importantes:



  • Context + useReducer () são recursos internos do React e não podem ser usados ​​fora dele. A loja Redux é independente da interface do usuário, então pode ser usada separadamente do React
  • React DevTools , . Redux DevTools , ( , type and payload),
  • useReducer() middleware. useEffect() useReducer(), useReducer() middleware, Redux middleware


Aqui está o que Sebastian Markbage (React Core Team Architect) disse sobre o uso de contexto :



“Minha opinião pessoal é que o novo contexto está pronto para ser usado para atualizações improváveis ​​de baixa frequência (como localização ou tema). Ele também pode ser usado em todos os casos em que o contexto antigo foi usado, ou seja, para valores estáticos com distribuição subsequente da atualização por assinatura. Ele não está pronto para ser usado como um substituto para distribuidores de estado semelhantes ao Flux. "



Existem muitos artigos na web que recomendam a configuração de vários contextos separados para diferentes partes do estado, evitando re-renderizações desnecessárias e resolvendo problemas de escopo. Algumas das postagens também sugerem adicionar seus próprios "componentes contextuais" , o que requer uma combinação de React.memo (), useMemo () e dividir ordenadamente o código em dois contextos para cada parte do aplicativo (um para dados, um para funções de atualização). Claro, você pode escrever código dessa maneira, mas neste caso você está reinventando o React-Redux.



Então, embora Context + useReducer () seja uma alternativa leve para Redux + React-Redux em uma primeira aproximação ... essas combinações não são idênticas, context + useReducer () não pode substituir Redux completamente!



Escolhendo a ferramenta certa



Para escolher a ferramenta certa, é muito importante entender quais tarefas a ferramenta resolve, bem como quais tarefas você enfrenta.



Visão geral dos casos de uso



  • Contexto

    • Transferência de dados para componentes aninhados sem "perfuração"


  • useReducer ()

    • Controlar o estado de um componente complexo usando uma função redutora


  • Context + useReducer ()

    • Gerenciar o estado de um componente complexo usando uma função redutora e transferir o estado para componentes aninhados sem "perfurar"


  • Restaurado

    • Controlar um estado muito complexo com funções redutoras
    • Rastreabilidade de quando, por que e como o estado mudou ao longo do tempo
    • Desejo de isolar completamente a lógica de gerenciamento de estado da camada de IU
    • Distribuir lógica de gerenciamento de estado entre diferentes camadas de IU
    • Usando os recursos de middleware para implementar lógica adicional ao inicializar operações
    • A capacidade de salvar certas partes do estado
    • Capacidade de obter relatórios de erros reproduzíveis
    • Capacidade de depurar rapidamente a lógica e a IU durante o desenvolvimento


  • Redux + React-Redux

    • Todos os casos de uso para Redux + a capacidade dos componentes React interagirem com a loja Redux




Mais uma vez: as ferramentas nomeadas resolvem problemas diferentes!



Recomendações



Então, como você decide o que usar?



Para fazer isso, você só precisa determinar qual ferramenta resolve melhor os problemas de sua aplicação.



  • Se você quer apenas evitar "furos", use o contexto
  • , , + useReducer()
  • , , .., Redux + React-Redux


Eu acredito que se sua aplicação tem 2-3 contextos para gerenciamento de estado, então você deve mudar para Redux.



Você ouvirá frequentemente que “usar o Redux envolve escrever muito código clichê”, entretanto, “Redux moderno” torna muito mais fácil de aprender e usar. O Redux Toolkit oficial resolve o problema de modelagem e os ganchos do React-Redux facilitam o uso do Redux nos componentes do React.



Obviamente, adicionar RTK e React-Redux como dependências aumenta o pacote de aplicativos sobre context + useReducer (), que são integrados. Mas as vantagens dessa abordagem cobrem as desvantagens - melhor rastreamento de estado, lógica mais simples e previsível, otimização de renderização de componente aprimorada.



Também é importante notar que um não exclui o outro - você pode usar Redux, Context e useReducer () juntos. Recomendamos armazenar o estado "global" no Redux e o estado local nos componentes, e tenha cuidado ao determinar qual parte da aplicação deve ser armazenada no Redux e qual nos componentes.... Portanto, você pode usar Redux para armazenar o estado global, Context + useReducer () para armazenar o estado local e Context para armazenar valores estáticos, todos simultaneamente no mesmo aplicativo.



Mais uma vez, não estou argumentando que todo o estado do aplicativo deve ser armazenado no Redux, ou que o Redux é sempre a melhor solução. Meu ponto é que Redux é uma boa escolha, há muitos motivos para usar o Redux e as taxas para usá-lo não são tão altas quanto muitos pensam.



Finalmente, o contexto e o Redux não são únicos. Existem muitas outras ferramentas que tratam de outros aspectos da gestão do estado. MobX é uma solução popular que usa OOP e observáveis ​​para atualizar automaticamente as dependências. Outras abordagens para a renovação do estado incluem Jotai, Recoil e Zustand. Bibliotecas de dados como React Query, SWR, Apollo e Urql fornecem abstrações que facilitam o uso de padrões comuns para trabalhar com o estado em cache do servidor (uma biblioteca semelhante ( RTK Query ) aparecerá para o Redux Toolkit em breve ).



Espero que este artigo tenha ajudado você a entender a diferença entre contexto e Redux, e qual ferramenta deve ser usada e quando. Obrigado pela atenção.



All Articles