Se você teve que trabalhar em um aplicativo em que mais de dois componentes com ancestrais diferentes tinham que compartilhar o mesmo estado, você entende que passar adereços para todos esses componentes pode ficar confuso rapidamente. O gerenciamento de estado é uma forma de gerenciar esses dados em nosso aplicativo, desde o valor de um campo de texto até as linhas de uma tabela.
Insira as bibliotecas de gerenciamento de estado, como Redux . Essas bibliotecas visavam resolver esse problema, mas ainda não eram perfeitas. A verdade é que a biblioteca de gerenciamento de estado perfeita não existe. Existem muitos fatores diferentes a serem considerados ao escolher um, como o tamanho do seu aplicativo, o que você deseja alcançar e quanto estado é compartilhado.
Neste artigo, examinaremos alguns opções de gerenciamento de estado para ajudá-lo a tomar uma decisão sobre qual usar em seus aplicativos React Native. Compararei a experiência do desenvolvedor de gerenciamento de estado com React Context API, Hookstate e Easy-Peasy.
Existem tantos artigos já escritos sobre gestores estaduais populares como Redux, então irei discutir esses menores para ajudá-lo a tomar uma decisão informada.
Pré-requisitos
Para acompanhar este artigo, você deve ter o seguinte:
Conhecimento prático de React e React Native Node.js, npm ou Yarn instalado em sua máquina (npm vem empacotado com Node. js) XCode ou Android Studio instalado em sua máquina As outras dependências necessárias descritas na documentação do React Native
Vou usar o Yarn para este artigo, mas se você preferir npm, certifique-se de substituir o comandos com os equivalentes do npm.
Configurando um aplicativo de demonstração
Devido à natureza deste artigo, não construiremos um novo aplicativo do zero. Como estarei apenas discutindo como essas bibliotecas se comparam, eu configurei um aplicativo de demonstração onde você pode acompanhar enquanto eu demonstro seus pontos fortes e fracos.
Clone o repo
Você pode encontre meu aplicativo de demonstração neste repositório Github . Se você cloná-lo localmente e instalar as dependências necessárias, verá que os branches foram criados para exemplos de cada biblioteca que discutiremos:
git clone https://github.com/edmund1645-demos/comparing-rn-state-lib
Instalar dependências
Depois de clonar o repo em sua máquina local, instale as dependências usando o gerenciador de pacotes de sua preferência:
npm install #or yarn install
Execute o aplicativo
Você pode dar uma olhada no branch principal, especialmente o arquivo App.js, para entender como o aplicativo está estruturado antes de implementarmos o gerenciamento de estado.
Execute o aplicativo usando este comando:
yarn ios #or npm #or yarn android
Gerenciando o estado com a API React Context
Estaremos examinando Context API primeiro. Agora, eu sei o que você está pensando: a API de contexto não é uma biblioteca”independente”. Embora isso seja verdade, ainda é uma opção que vale a pena considerar.
Verifique o branch de exemplo de contexto depois de clonar o repo e instalar as dependências:
git checkout context-example
Agora, pegue um veja o arquivo contexts/CartContext.js:
import React, {createContext} from’react’; export const initialState={size: 0, products: {},}; exportar const CartContext=createContext (initialState);
Usamos o método createContext do React para criar um objeto de contexto e exportá-lo. Também passamos um valor padrão.
No App.js, primeiro importamos o objeto CartContext e o valor padrão initialState.
Após a importação, precisamos configurar um gancho useReducer para modificar o estado com base no tipo de ação:
//importar contexto import {CartContext, initialState} de’./contexts/CartContext.js’;//função do redutor const redutor=(estado, ação)=> {switch (action.type) {case’ADD_TO_CART’: if (! state.products [`item-$ {action.payload.id}`]) {return {size: (state.size +=1), produtos: {… state.products, [`item-$ {action.payload.id}`]: {… action.payload, quantidade: 1}} }; } else {let productsCopy={… state.products}; productsCopy [`item-$ {action.payload.id}`].quantity +=1; return {size: (state.size +=1), products: productsCopy}; }}}; exportar a função padrão App () {//configurar o gancho do redutor const [state, dispatch]=useReducer (reducer, initialState);//crie um Provedor e use os valores de retorno do gancho redutor como o valor de retorno do Provedor (
Ao configurar um contexto com valores que precisam ser modificados, como em nosso exemplo, precisamos usar um gancho redutor. Você notará que estamos usando os valores desse gancho redutor no Provedor no código acima. Isso ocorre porque a função redutora atualiza o estado, portanto, queremos disponibilizar o estado (e a função para modificá-lo) em todos os nossos componentes.
Um pouco mais tarde, você verá por que os componentes filhos tenha acesso ao valor do Provedor e não ao valor padrão com o qual o contexto foi criado.
A seguir, dê uma olhada no arquivo components/ProductCard.jsx:
import React, {useContext } from’react’import {CartContext} from’../contexts/CartContext’const ProductCard=({product})=> {const [state, dispatch]=useContext (CartContext) function addToCart () {dispatch ({type:’ADD_TO_CART’, payload: product})} return ({/* children */})}
Para acessar os valores com os quais o contexto do carrinho foi criado, precisamos importá-lo e passá-lo para o gancho useContext.
Observe como o valor retornado é a matriz que passamos para o provedor anteriormente, e não o valor padrão com o qual o contexto foi criado. Isso ocorre porque ele usa o valor do Provedor correspondente na árvore; se não houvesse CartContext.Provider na árvore, o valor retornado seria initialState.
Quando o botão do carrinho é clicado, addToCart é invocado e uma ação é enviada para nossa função redutora para atualizar o estado. Se você olhar para a função do redutor novamente, notará que um objeto está sendo retornado; este objeto é o novo estado.
Cada vez que despachamos uma ação, um novo estado é retornado apenas para atualizar uma única propriedade naquele grande objeto.
Vamos dar uma olhada na tela do carrinho (telas/Cart.jsx):
import React, {useContext} from’react’import {CartContext} from’../contexts/CartContext’const Cart=()=> {const [estado, despacho]=useContext (CartContext) return ({/* children */)}
Aqui estamos usando o mesmo padrão que ProductCard.jsx, só que desta vez estamos usando apenas o estado para renderizar itens do carrinho.
Prós de usar a API Context e useReducer
Ideal para projetos pequenos Não afeta o tamanho do pacote
Contras de usar a API Context com useReducer
Atualizar objetos grandes pode ficar confuso rapidamente Pode ser inadequado para grandes projetos, porque você precisa empilhar vários Provedores na árvore se houver necessidade
Gerenciando o estado com Hookstate
Hookstate vem com uma abordagem diferente para gerenciamento de estado. É simples o suficiente para aplicativos pequenos e flexível o suficiente para aplicativos relativamente grandes.
Verifique o branch hookstate-example:
git checkout hookstate-example
Com o Hookstate, usamos o conceito de estado global no estado/Cart.js. A biblioteca exporta duas funções: createState para criar um novo estado envolvendo algumas propriedades e métodos em torno do estado padrão e retornando-o, e useState para usar o estado retornado de createState ou outro useState.
import {createState, useState} de’@ hookstate/core’; const cartState=createState ({size: 0, products: {},}); export const useGlobalState=()=> {const cart=useState (cartState); return {get: ()=> cart.value, addToCart: (product)=> {if (cart.products [`item-$ {product.id}`].value) {cart.products [`item-$ { product.id} `].merge ({quantidade: cart.products [` item-$ {product.id} `].quantity.value + 1}); cart.size.set (cart.size.value + 1); } else {cart.products.merge ({[`item-$ {product.id}`]: {… produto, quantidade: 1}}); cart.size.set (cart.size.value + 1); }},}; };
Com a forma como o Hookstate é estruturado, também podemos exportar uma função auxiliar para interagir com os componentes dentro do estado.
Tudo o que precisamos fazer é importar useGlobalState, invocá-lo em um componente funcional e desestruturar qualquer um dos métodos dos objetos retornados (dependendo do que queremos alcançar).
Aqui está um exemplo de como usamos o método addToCart em components/ProductCard.jsx:
import {useGlobalState} de’../state/Cart’; const ProductCard=({product})=> {//invocar a função para retornar o objeto const state=useGlobalState () function addToCart () {//passar o produto e deixar a função auxiliar lidar com o estado restante.addToCart (product )} return ({/* products */})}
E na página Carrinho em/screens/Cart.js:
import {useGlobalState} from’../state/Cart’; const Cart=()=> {const {products}=useGlobalState (). get () return ({/* renderizar todos os itens do carrinho aqui */})}
A melhor parte do Hookstate é que todas as propriedades ou método (aninhado e no nível superior) no estado global é um tipo de estado e tem vários métodos para se modificar diretamente. É reativo o suficiente para atualizar o estado em todos os componentes do aplicativo.
Prós de usar Hookstate
APIs fáceis para fazer o trabalho Performant Muitas extensões para criar mais aplicativos de alcance de recursos Sistema totalmente tipado
Contras do uso do Hookstate
Eu sei que disse que não havia alternativas”perfeitas”, mas parece que o Hookstate está tentando refutar meu teoria. No entanto, há um fator desprezível a ser considerado: Hookstate não é muito conhecido-tem cerca de 3.000 downloads semanais em npm , então há uma chance de que a comunidade ao redor seja pequena.
Gerenciando o estado com Easy-Peasy
Easy-Peasy é uma abstração do Redux, construída para expor uma API fácil que melhora muito a experiência do desenvolvedor, mantendo todos os benefícios que o Redux tem a oferecer.
Percebi que trabalhar com Easy-Peasy é como trabalhar com uma combinação dos dois exemplos acima, porque você tem que envolver todo o aplicativo em torno de um provedor (não se preocupe, você só precisa fazer isso uma vez, a menos que você queira estados modulares).
Para importar Easy-Peasy, copie o seguinte em App.js:
import {StoreProvider} from’easy-peasy’; importar cartStore de’./state/cart’; função padrão de exportação App () {return (<>
Você pode importar ganchos da biblioteca para selecionar partes específicas do estado global que você precisa dentro de seus componentes.
Vamos dar uma olhada em/state/Cart.js:
import {createStore, action} de’easy-peasy’; exportar createStore padrão ({size: 0, products: {}, addProductToCart: action ((state, payload)=> {if (state.products [`item-$ {payload.id}`]) {state.products [` item-$ {payload.id} `].quantity +=1; state.size +=1;} else {state.products [` item-$ {payload.id} `]={… payload, quantidade: 1}; state.size +=1;}}),});
Usamos createStore para abrir uma loja global. O objeto passado é denominado “modelo”. Ao definir o modelo, também podemos incluir propriedades como ações . As ações nos permitem atualizar o estado na loja.
Em components/ProductCard.jsx, queremos usar a ação addProductToCart, então usamos o gancho useStoreActions do Easy-Peasy:
import Reagir de’reagir’; import {useStoreActions} de’easy-peasy’; const ProductCard=({product})=> {const addProductToCart=useStoreActions ((actions)=> actions.addProductToCart) function addToCart () {addProductToCart (product)} return ({/* children */})}
Se queríamos usar o estado em um componente, usamos o gancho useStoreState como visto em screens/Cart.jsx:
import React from’react’; import {useStoreState} de’easy-peasy’; const Cart=()=> {const products=useStoreState ((state)=> state.products) return ({/* children */})}
Prós de usar Easy-Peasy
Totalmente reativo integrado Redux para que haja suporte para ferramentas Redux Dev e mais Easy APIs
Contras do uso do Easy-Peasy
Maior tamanho do pacote. Se isso é um grande negócio para você, Easy-Peasy pode não ser sua biblioteca ideal
Conclusão
Neste artigo, vimos a comparação entre a API de contexto com ganchos, Hookstate e Easy-Peasy.
Para resumir, usar a API de contexto com ganchos em projetos de demonstração seria o ideal, mas quando seus aplicativos começam a crescer em tamanho, fica difícil de manter. É aqui que o Hookstate e o Easy-Peasy brilham.
O Hookstate e o Easy-Peasy apresentam APIs fáceis para gerenciar o estado e apresentam desempenho de maneiras exclusivas. O Easy-Peasy foi desenvolvido sobre o Redux para que você tenha esses benefícios adicionais, e o Hookstate tem um conjunto de extensões para implementar recursos em seu aplicativo, como persistência de armazenamento local para o estado.
Muitas alternativas não foram mencionadas neste artigo devido ao comprimento, então aqui estão algumas menções honrosas:
Você pode encontrar o repositório para este projeto aqui , caso você queira inspecionar o código de cada exemplo.