Crear una tienda global tipo Redux usando React Hooks

Hola Habr! Le presento la traducción del artículo "Construya una tienda global similar a Redux usando React Hooks" de Ramsay.


Imaginemos que escribí una introducción interesante a este artículo, y ahora podemos ir directamente a las cosas realmente interesantes. En resumen, lo haremos
use useReducer y useContext para crear un enlace React personalizado que proporcione acceso a un repositorio global similar a Redux.


De ninguna manera asumo que esta solución es el equivalente completo de Redux, porque estoy seguro de que no lo es. Al decir "como Redux", quiero decir,
que actualizará el repositorio utilizando despacho y acciones , que mutarán el estado del repositorio y devolverán una nueva copia del estado mutado.
Si nunca ha usado Redux, solo simule no leer este párrafo.


Ganchos


Comencemos creando un contexto (en adelante Contexto ) que contendrá nuestro estado (en adelante estado ) y una función de despacho (en adelante despacho ). También crearemos la función useStore , que se comportará como nuestro 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 }; }; 

Dado que todo está almacenado dentro del contexto de reacción , debe crear un proveedor que proporcione
nosotros un objeto de estado y una función de despacho . El proveedor es donde usamos 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 obtener estado y despacho . En realidad, esto es exactamente lo que hace useReducer . Luego, pasamos el estado y enviamos al Proveedor .
Ahora podemos ajustar cualquier componente React usando <Provider /> y este componente puede usar useStore para interactuar con el repositorio.


No hemos creado reductor todavía . Este será nuestro próximo paso.


 // 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 }; }; ... 

Soy un gran admirador de separar las acciones y el estado en grupos lógicos, por ejemplo: puede que necesite monitorear el estado del contador (un ejemplo clásico de la implementación del contador) o el estado del usuario (si el usuario ha iniciado sesión en el sistema o sus preferencias personales).
En algunos componentes, es posible que necesite acceder a ambos estados, por lo que la idea de almacenarlos en un único repositorio global tiene mucho sentido. Podemos dividir nuestras acciones en grupos lógicos como userActions y countActions , lo que hace que administrarlas sea mucho más fácil.


Creemos los archivos countActions.js y userActions.js en la carpeta de la tienda.


 // 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 } }; } }; 

En ambos archivos, exportamos initialState , porque queremos combinarlos luego en el archivo useStore.js en un solo objeto initialState .
También exportamos un objeto Actions que proporciona funciones para mutaciones de estado. Tenga en cuenta que no estamos devolviendo un nuevo objeto de estado, porque queremos que esto suceda en reductor , en el archivo useStore.js .


Ahora lo importamos todo en useStore.js para obtener la imagen 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 }; }; 

Lo hicimos! Haga un círculo de honor, y cuando regrese, veremos cómo usarlo todo en el componente.


Bienvenido de nuevo! Espero que tu círculo haya sido verdaderamente honorable. Echemos un vistazo a useStore en acción.


Primero podemos envolver nuestro componente de aplicación en <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); 

Empaquetamos la aplicación en StoreProvider para que el componente secundario tenga acceso al valor del proveedor. Este valor es tanto de estado como de envío .


Ahora, supongamos que tenemos un componente AppHeader que tiene un botón de inicio / cierre de sesión.


 // 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; 

Enlace a Code Sandbox con implementación completa


Autor original: Ramsay
Enlace al original

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


All Articles