Hola a todos! Mi nombre es Arthur, trabajo en VKontakte como un equipo web móvil, estoy involucrado en el proyecto 
VKUI , una biblioteca de componentes React, con la ayuda de la cual se escriben algunas de nuestras interfaces en aplicaciones móviles. La cuestión de trabajar con un estado global todavía está abierta para nosotros. Existen varios enfoques bien conocidos: Redux, MobX, Context API. Recientemente me encontré con un artículo de André Gardi 
State Management con React Hooks - No Redux o Context API , en el que el autor sugiere usar React Hooks para controlar el estado de la aplicación.
Los ganchos están entrando rápidamente en la vida de los desarrolladores, ofreciendo nuevas formas de resolver o repensar diferentes tareas y enfoques. Cambian nuestra comprensión no solo de cómo describir componentes, sino también de cómo trabajar con datos. Lea la traducción del artículo y el comentario del traductor debajo del gato.

Los ganchos de reacción son más poderosos de lo que piensas
Hoy estudiaremos React Hooks y desarrollaremos un hook personalizado para administrar el estado global de la aplicación, que será más simple que la implementación de Redux y más productivo que la API Context.
Conceptos básicos de React Hooks
Puede omitir esta parte si ya está familiarizado con los ganchos.
useState ()
Antes de la aparición de los ganchos, los componentes funcionales no tenían la capacidad de establecer un estado local. La situación ha cambiado con la llegada de 
useState() .

Esta llamada devuelve una matriz. Su primer elemento es una variable que proporciona acceso al valor de estado. El segundo elemento es una función que actualiza el estado y vuelve a dibujar el componente para reflejar los cambios.
 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 ()
Los componentes de la clase responden a los efectos secundarios utilizando métodos de ciclo de vida como 
componentDidMount() . El gancho 
useEffect() permite hacer lo mismo en componentes funcionales.
Por defecto, los efectos se activan después de cada redibujado. Pero puede asegurarse de que se ejecuten solo después de cambiar los valores de variables específicas, pasándoles el segundo parámetro opcional en forma de matriz.
 
Para lograr un resultado similar a 
componentDidMount() , pasaremos una matriz vacía al segundo parámetro. Como el contenido de una matriz vacía siempre permanece sin cambios, el efecto se ejecutará solo una vez.
 
Estado compartido
Vimos que un estado de enlace funciona igual que un estado de componente de clase. Cada instancia de componente tiene su propio estado interno.
Para compartir el estado entre los componentes, crearemos nuestro propio gancho.

La idea es crear una variedad de oyentes y un solo estado. Cada vez que un componente cambia de estado, todos los componentes suscritos llaman a su 
getState() y se actualizan debido a esto.
Podemos lograr esto llamando a 
useState() dentro de nuestro 
useState() personalizado. Pero en lugar de devolver la función 
setState() , la agregamos a la matriz de oyentes y devolvemos una función que actualiza internamente el objeto de estado y llama a todos los oyentes.
Espera un momento ¿Cómo me facilita la vida?
Si tienes razon. 
Creé un 
paquete NPM que encapsula toda la lógica descrita.
No tiene que implementarlo en cada proyecto. Si ya no desea pasar tiempo leyendo y desea ver el resultado final, simplemente agregue este paquete a su aplicación.
 npm install -s use-global-hook 
Para comprender cómo trabajar con un paquete, estudie ejemplos en la documentación. Y ahora propongo centrarme en cómo se organiza el paquete por dentro.
Primera versión
 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; 
Uso en 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 versión ya proporciona el estado de compartir. Puede agregar un número arbitrario de contadores a su aplicación, y todos tendrán un estado global común.
Pero podemos hacerlo mejor
Que quieres
- eliminar el oyente de la matriz al desmontar el componente;
- hacer el gancho más abstracto para usar en otros proyectos;
- gestionar initialStateusando parámetros;
- reescribe el gancho en un estilo más funcional.
Llamar a una función justo antes de desmontar un componente
Ya descubrimos que llamar a 
useEffect(function, []) con una matriz vacía funciona de la misma manera que 
componentDidMount() . Pero si la función pasada en el primer parámetro devuelve otra función, entonces se llamará a la segunda función justo antes de desmontar el componente. Exactamente igual que 
componentWillUnmount() .
Entonces, en el código de la segunda función, puede escribir la lógica para eliminar un componente de una matriz de oyentes.
 const useCustom = () => { const newListener = useState()[1]; useEffect(() => {  
Segunda versión
Además de esta actualización, también planeamos:
- pasar el parámetro React y deshacerse de la importación;
- export no customHook, sino una función que devuelve customHook con el initalStatedado;
- crear un objeto de storeque contendrá el valor destatey la funciónsetState();
- reemplace las funciones de flecha con las habituales en setState()yuseCustom()para que pueda asociar lastoreconthis.
 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(() => {  
Separar acciones de componentes
Si alguna vez trabajó con bibliotecas de administración de estado complejas, entonces sabe que manipular un estado global a partir de componentes no es una buena idea.
Sería más correcto separar la lógica de negocios creando acciones para cambiar el estado. Por lo tanto, quiero que la última versión del paquete proporcione acceso a los componentes no a 
setState() , sino a un conjunto de acciones.
Para hacer esto, proporcionamos nuestro 
useGlobalHook(React, initialState, actions) tercer argumento. Solo quiero agregar un par de comentarios.
- Las acciones tendrán acceso a la store. De esta forma, las acciones pueden leer el contenido destore.state, actualizar elstore.setState()llamando astore.setState()e incluso llamar a otrasstore.actions.
- Para evitar problemas, el objeto de acción puede contener subobjetos. Por lo tanto, puede transferir actions.addToCounter(amount) a un subobjeto con todas las acciones de contador:actions.counter.add(amount).
Versión final
El siguiente fragmento es la versión actual del paquete 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; 
Ejemplos de uso
Ya no tiene que lidiar con 
useGlobalHook.js . Ahora puede concentrarse en su aplicación. Los siguientes son dos ejemplos de uso del paquete.
Múltiples contadores, un valor
Agregue tantos contadores como desee: todos tendrán un valor global. Cada vez que uno de los contadores incremente el estado global, todos los demás serán redibujados. En este caso, el componente padre no necesita volver a dibujar.
Vivir ejemplo .
Solicitudes asincrónicas ajax
Buscar repositorios de GitHub por nombre de usuario. Procesamos solicitudes ajax de forma asincrónica usando async / await. Actualizamos el contador de consultas con cada nueva búsqueda.
Vivir ejemplo .
Bueno eso es todo
Ahora tenemos nuestra propia biblioteca de administración de estado en React Hooks.
Comentario del traductor
La mayoría de las soluciones existentes son esencialmente bibliotecas separadas. En este sentido, el enfoque descrito por el autor es interesante en el sentido de que utiliza solo las funciones integradas de React. Además, en comparación con la misma API de contexto, que también viene de fábrica, este enfoque reduce el número de redibujos innecesarios y, por lo tanto, gana en rendimiento.