Olá pessoal! Meu nome é Arthur, trabalho no VKontakte como uma equipe da web móvel, estou envolvido no projeto
VKUI - uma biblioteca de componentes do React, com a ajuda da qual algumas de nossas interfaces em aplicativos móveis são escritas. A questão de trabalhar com um estado global ainda está aberta para nós. Existem várias abordagens conhecidas: Redux, MobX, Context API. Recentemente, deparei com um artigo de André Gardi
State Management com React Hooks - No Redux ou Context API , no qual o autor sugere o uso do React Hooks para controlar o estado do aplicativo.
Os ganchos estão entrando rapidamente na vida dos desenvolvedores, oferecendo novas maneiras de resolver ou repensar diferentes tarefas e abordagens. Eles mudam nossa compreensão não apenas de como descrever componentes, mas também de como trabalhar com dados. Leia a tradução do artigo e o comentário do tradutor abaixo do gato.

Os ganchos de reação são mais poderosos do que você imagina
Hoje, estudaremos o React Hooks e desenvolveremos um gancho personalizado para gerenciar o estado global do aplicativo, que será mais simples que a implementação do Redux e mais produtivo que a API do Contexto.
React Hooks Basics
Você pode pular esta parte se já estiver familiarizado com os ganchos.
useState ()
Antes do aparecimento dos ganchos, os componentes funcionais não tinham a capacidade de definir um estado local. A situação mudou com o advento de
useState()
.

Essa chamada retorna uma matriz. Seu primeiro elemento é uma variável que fornece acesso ao valor do estado. O segundo elemento é uma função que atualiza o estado e redesenha o componente para refletir as alterações.
import React, { useState } from 'react'; function Example() { const [state, setState] = useState({counter:0}); const add1ToCounter = () => { const newCounterValue = state.counter + 1; setState({ counter: newCounterValue}); } return ( <div> <p>You clicked {state.counter} times</p> <button onClick={add1ToCounter}> Click me </button> </div> ); }
useEffect ()
Os componentes de classe respondem a efeitos colaterais usando métodos de ciclo de vida como
componentDidMount()
. O gancho
useEffect()
permite fazer o mesmo em componentes funcionais.
Por padrão, os efeitos são acionados após cada redesenho. Mas você pode garantir que eles sejam executados somente após alterar os valores de variáveis específicas, passando o segundo parâmetro opcional na forma de uma matriz.
Para obter um resultado semelhante ao
componentDidMount()
, passaremos uma matriz vazia para o segundo parâmetro. Como o conteúdo de uma matriz vazia sempre permanece inalterado, o efeito será executado apenas uma vez.
Partilha de Estado
Vimos que um estado de gancho funciona exatamente como um estado de componente de classe. Cada instância do componente possui seu próprio estado interno.
Para compartilhar o estado entre os componentes, criaremos nosso próprio gancho.

A idéia é criar uma matriz de ouvintes e apenas um estado. Sempre que um componente muda de estado, todos os componentes assinados chamam
getState()
e são atualizados devido a isso.
Podemos conseguir isso chamando
useState()
dentro de nosso gancho personalizado. Mas, em vez de retornar a função
setState()
, a adicionamos à matriz de ouvintes e retornamos uma função que atualiza internamente o objeto state e chama todos os ouvintes.
Espere um momento. Como isso facilita minha vida?
Sim, você está certo. Criei um
pacote NPM que encapsula toda a lógica descrita.
Você não precisa implementá-lo em todos os projetos. Se você não deseja mais gastar tempo lendo e deseja ver o resultado final, basta adicionar este pacote ao seu aplicativo.
npm install -s use-global-hook
Para entender como trabalhar com um pacote, estude exemplos na documentação. E agora eu proponho me concentrar em como o pacote está organizado dentro.
Primeira versão
import { useState, useEffect } from 'react'; let listeners = []; let state = { counter: 0 }; const setState = (newState) => { state = { ...state, ...newState }; listeners.forEach((listener) => { listener(state); }); }; const useCustom = () => { const newListener = useState()[1]; useEffect(() => { listeners.push(newListener); }, []); return [state, setState]; }; export default useCustom;
Use no componente
import React from 'react'; import useCustom from './customHook'; const Counter = () => { const [globalState, setGlobalState] = useCustom(); const add1Global = () => { const newCounterValue = globalState.counter + 1; setGlobalState({ counter: newCounterValue }); }; return ( <div> <p> counter: {globalState.counter} </p> <button type="button" onClick={add1Global}> +1 to global </button> </div> ); }; export default Counter;
Esta versão já fornece o estado de compartilhamento. Você pode adicionar um número arbitrário de contadores ao seu aplicativo, e todos eles terão um estado global comum.
Mas podemos fazer melhor
O que você quer:
- remova o ouvinte da matriz ao desmontar o componente;
- tornar o gancho mais abstrato para usar em outros projetos;
- gerenciar
initialState
usando parâmetros; - reescreva o gancho em um estilo mais funcional.
Chamando uma função antes de desmontar um componente
Já descobrimos que a chamada
useEffect(function, [])
com uma matriz vazia funciona da mesma maneira que
componentDidMount()
. Mas se a função passada no primeiro parâmetro retornar outra função, a segunda função será chamada imediatamente antes de desmontar o componente. Exatamente como
componentWillUnmount()
.
Portanto, no código da segunda função, você pode escrever a lógica para remover um componente de uma matriz de ouvintes.
const useCustom = () => { const newListener = useState()[1]; useEffect(() => {
Segunda versão
Além desta atualização, também planejamos:
- passe o parâmetro React e livre-se da importação;
- exportar não customHook, mas uma função que retorna customHook com o dado
initalState
; - crie um objeto de
store
que conterá o valor do state
e a função setState()
; - substitua as funções de seta pelas usuais em
setState()
e useCustom()
para que você possa associar a store
a this
.
function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => {
Separe as ações dos componentes
Se você já trabalhou com bibliotecas complexas de gerenciamento de estado, sabe que manipular um estado global a partir de componentes não é uma boa ideia.
Seria mais correto separar a lógica de negócios criando ações para alterar o estado. Portanto, desejo que a versão mais recente do pacote forneça acesso aos componentes não a
setState()
, mas a um conjunto de ações.
Para fazer isso, fornecemos nosso
useGlobalHook(React, initialState, actions)
. Só quero adicionar alguns comentários.
- As ações terão acesso à
store
. Dessa forma, as ações podem ler o conteúdo de store.state
, atualizar o store.setState()
chamando store.setState()
e até chamar outras store.actions
. - Para evitar confusão, o objeto de ação pode conter subobjetos. Assim, você pode transferir
actions.addToCounter(amount
) para um subobjeto com todas as ações do contador: actions.counter.add(amount)
.
Versão final
O seguinte snippet é a versão atual do pacote NPM
use-global-hook
.
function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); }); } function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { this.listeners.push(newListener); return () => { this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.actions]; } function associateActions(store, actions) { const associatedActions = {}; Object.keys(actions).forEach((key) => { if (typeof actions[key] === 'function') { associatedActions[key] = actions[key].bind(null, store); } if (typeof actions[key] === 'object') { associatedActions[key] = associateActions(store, actions[key]); } }); return associatedActions; } const useGlobalHook = (React, initialState, actions) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); store.actions = associateActions(store, actions); return useCustom.bind(store, React); }; export default useGlobalHook;
Exemplos de uso
Você não precisa mais lidar com o
useGlobalHook.js
. Agora você pode se concentrar em seu aplicativo. A seguir, dois exemplos de uso do pacote.
Vários contadores, um valor
Adicione quantos contadores quiser: todos eles terão um valor global. Cada vez que um dos contadores aumentará o estado global, todos os outros serão redesenhados. Nesse caso, o componente pai não precisa ser redesenhado.
Exemplo vivo .
Solicitações assíncronas de ajax
Pesquise repositórios do GitHub por nome de usuário. Processamos solicitações ajax de forma assíncrona usando async / waitit. Atualizamos o contador de consultas a cada nova pesquisa.
Exemplo vivo .
Bem, isso é tudo
Agora temos nossa própria biblioteca de gerenciamento de estado no React Hooks.
Comentário do tradutor
A maioria das soluções existentes são essencialmente bibliotecas separadas. Nesse sentido, a abordagem descrita pelo autor é interessante, pois utiliza apenas os recursos internos do React. Além disso, comparada à mesma API de contexto, que também sai da caixa, essa abordagem reduz o número de redesenhos desnecessários e, portanto, obtém desempenho.