Criando uma loja global semelhante ao Redux usando ganchos de reação

Olá Habr! Apresento a você a tradução do artigo "Construa uma loja global semelhante ao Redux usando ganchos de reação" de Ramsay.


Vamos imaginar que eu escrevi uma introdução interessante a este artigo, e agora podemos ir direto às coisas realmente interessantes. Em suma, vamos
use useReducer e useContext para criar um gancho React personalizado que fornece acesso a um repositório global semelhante ao Redux.


Não suponho de forma alguma que esta solução seja o equivalente total do Redux, porque tenho certeza de que não. Ao dizer "Redux-like", quero dizer,
que você atualizará o repositório usando despacho e ações , o que mudará o estado do repositório e retornará uma nova cópia do estado mutado.
Se você nunca usou o Redux, apenas finja não ler este parágrafo.


Hooks


Vamos começar criando um contexto ( daqui em diante Contexto ) que conterá nosso estado ( daqui em diante ) e uma função de despacho (a seguir ). Também criaremos a função useStore , que se comportará como nosso gancho.


// store/useStore.js import React, { createContext, useReducer, useContext } from "react"; //     const initialState = {} const StoreContext = createContext(initialState); // useStore    React       export const useStore = store => { const { state, dispatch } = useContext(StoreContext); return { state, dispatch }; }; 

Como tudo é armazenado dentro do contexto de reação , você precisa criar um provedor que fornecerá
nós um objeto de estado e uma função de despacho . O provedor é onde usamos o useReducer .


 // store/useStore.js ... const StoreContext = createContext(initialState); export const StoreProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreContext.Provider value={{ state, dispatch }}> {children} </StoreContext.Provider> ); }; ... 

Usamos useReducer para obter o estado e o envio . Na verdade, é exatamente isso que useReducer . Em seguida, passamos o estado e o despacho para o Provedor .
Agora podemos agrupar qualquer componente React usando <Provider /> e esse componente pode usar useStore para interagir com o repositório.


Ainda não criamos redutor . Este será o nosso próximo passo.


 // store/useStore.js ... const StoreContext = createContext(initialState); //    actions,     state const Actions = {}; // reducer   ,  action    dispatch // action.type -  ,     Actions //   update   state      const reducer = (state, action) => { const act = Actions[action.type]; const update = act(state); return { ...state, ...update }; }; ... 

Sou um grande fã de separar ações e estados em grupos lógicos, por exemplo: pode ser necessário monitorar o estado do contador (um exemplo clássico da implementação do contador) ou o estado do usuário (se o usuário efetuou login no sistema ou suas preferências pessoais).
Em algum componente, você pode precisar acessar esses dois estados; portanto, a idéia de armazená-los em um único repositório global faz todo o sentido. Podemos dividir nossas ações em grupos lógicos, como userActions e countActions , o que facilita o gerenciamento delas.


Vamos criar os arquivos countActions.js e userActions.js na pasta da loja.


 // store/countActions.js export const countInitialState = { count: 0 }; export const countActions = { increment: state => ({ count: state.count + 1 }), decrement: state => ({ count: state.count - 1 }) }; 

 // store/userActions.js export const userInitialState = { user: { loggedIn: false } }; export const userActions = { login: state => { return { user: { loggedIn: true } }; }, logout: state => { return { user: { loggedIn: false } }; } }; 

Nos dois arquivos, exportamos initialState , porque queremos combiná-los posteriormente no arquivo useStore.js em um único objeto initialState .
Também exportamos um objeto Actions que fornece funções para mutações de estado. Observe que não estamos retornando um novo objeto de estado, porque queremos que isso aconteça no redutor , no arquivo useStore.js .


Agora importamos tudo para useStore.js para obter uma imagem completa.


 // store/useStore.js import React, { createContext, useReducer, useContext } from "react"; import { countInitialState, countActions } from "./countActions"; import { userInitialState, userActions } from "./userActions"; //    (initial states) const initialState = { ...countInitialState, ...userInitialState }; const StoreContext = createContext(initialState); //  actions const Actions = { ...userActions, ...countActions }; const reducer = (state, action) => { const act = Actions[action.type]; const update = act(state); return { ...state, ...update }; }; export const StoreProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreContext.Provider value={{ state, dispatch }}> {children} </StoreContext.Provider> ); }; export const useStore = store => { const { state, dispatch } = useContext(StoreContext); return { state, dispatch }; }; 

Nós conseguimos! Faça um círculo de honra e, quando você voltar, veremos como usar tudo isso no componente.


Bem-vindo de volta! Espero que seu círculo tenha sido verdadeiramente honrado. Vamos dar uma olhada no useStore em ação.


Primeiro, podemos agrupar nosso componente App em <StoreProvider /> .


 // App.js import React from "react"; import ReactDOM from "react-dom"; import { StoreProvider } from "./store/useStore"; import App from "./App"; function Main() { return ( <StoreProvider> <App /> </StoreProvider> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<Main />, rootElement); 

Envolvemos o aplicativo no StoreProvider para que o componente filho tenha acesso ao valor do provedor. Este valor é estado e expedição .


Agora, vamos assumir que temos um componente do AppHeader que possui um botão de login / logout.


 // AppHeader.jsx import React, {useCallback} from "react"; import { useStore } from "./store/useStore"; const AppHeader = props => { const { state, dispatch } = useStore(); const login = useCallback(() => dispatch({ type: "login" }), [dispatch]); const logout = useCallback(() => dispatch({ type: "logout" }), [dispatch]); const handleClick = () => { loggedIn ? logout() : login(); } return ( <div> <button onClick={handleClick}> {loggedIn ? "Logout" : "Login"}</button> <span>{state.user.loggedIn ? "logged in" : "logged out"}</span> <span>Counter: {state.count}</span> </div> ); }; export default AppHeader; 

Link para Code Sandbox com implementação completa


Autor original: Ramsay
Link para o original

Source: https://habr.com/ru/post/pt460083/


All Articles