Información

Un tutorial completo de React Redux para principiantes (2019)

Noticias & Blog

Image link
Un tutorial completo de Redux (2019): ¿por qué usarlo? - tienda - reductores - acciones - thunks - obtención de datos

Al intentar comprender Redux, resulta realmente confuso cómo funciona todo. Especialmente como principiante.

¡Cuánta terminología! Acciones, reductores, creadores de acciones, middleware, funciones puras, inmutabilidad, thunks…

¿Cómo encaja todo con React para crear una aplicación que funcione?

Puedes pasar horas leyendo blogs e intentando revisar aplicaciones complejas “del mundo real” intentando reconstruirlas.

En este tutorial de Redux voy a explicar cómo usar Redux con React de forma incremental – comenzando con React simple – y un ejemplo muy simple de React + Redux. Te lo explicaré por qué Cada característica es útil (y puedes omitir algunas).

Luego veremos los temas más avanzados, uno por uno, hasta que lo entiendas Todo ello. Allá vamos 🙂

Lo esencial de Redux en un solo vídeo

Si prefieres mirar antes que leer, este video explica paso a paso cómo agregar Redux a una aplicación React:

Tutorial sencillo de Redux: cómo agregar Redux a una aplicación React simple

Esto es paralelo a la primera parte de este tutorial, donde tomaremos una aplicación React simple y le agregaremos Redux pieza por pieza.

¡O sigue leyendo! El siguiente tutorial cubre todo lo que hay en el vídeo y algo más.

¿Deberías utilizar Redux?

Es especialmente válido, en 2020, preguntarse… ¿deberías seguir usando Redux? ¿Hay algo mejor ahora, con Hooks o Context o alguna otra biblioteca?

La respuesta corta: incluso con muchas alternativas, Redux aún no ha muerto. Pero si tiene sentido o no para tu aplicación… bueno, depende.

¿Súper simple? ¿Sólo unos pocos fragmentos de estado en uno o dos lugares? El estado del componente local probablemente será excelente. Puedes hacer eso con las clases, Ganchos, o ambos.

¿Un poco más complejo, con algunas cosas “globales” que deben compartirse en toda la aplicación? El API de contexto Podría ser perfecto para ti.

¿Mucho estado global, con interacciones entre partes desconectadas de la aplicación? ¿O una gran aplicación que sólo crecerá con el tiempo? Prueba Redux.

Siempre puedes agregar Redux más tarde, también. No tienes que decidir el día 1. Comience de forma sencilla y agregue complejidad cuando y donde la necesite.

¿Ya conoces React?

React se puede utilizar solo sin Redux. Redux es un add-on Reaccionar.

Incluso si tienes la intención de usarlos ambos, te recomiendo encarecidamente que aprendas sólo Reaccionar sin Redux inicialmente. Comprenda las propiedades, el estado y el flujo de datos unidireccional, y aprenda a “pensar en React” antes de intentar aprender Redux. Aprenderlos a ambos al mismo tiempo es una receta segura para la confusión. Tengo un Tutorial detallado de React que cubre todas las cosas importantes que necesitas saber.

Los beneficios de Redux

Si has usado React durante más de unos minutos, probablemente conozcas las propiedades y el flujo de datos unidireccional. Se pasan datos abajo el árbol de componentes a través de accesorios. Dado un componente como este:

Contracomponente

El count, almacenado en App’s estado, se transmitiría como accesorio:

Pasando accesorios hacia abajo

Para que los datos regresen arriba el árbol debe fluir a través de una función de devolución de llamada, por lo que primero se debe pasar esa función de devolución de llamada abajo a cualquier componente que quiera llamarlo para pasar datos.

Pasando devoluciones de llamadas hacia abajo

Puedes pensar en los datos como electricidad, conectado mediante cables de colores a los componentes que lo cuidan. Los datos fluyen hacia abajo y hacia arriba a través de estos cables, pero los cables no pueden pasar por el aire – deben encadenarse de un componente al siguiente.

Pasar datos de varios niveles es una molestia

Tarde o temprano te encuentras con una situación en la que un contenedor de nivel superior tiene algunos datos y un niño de 4+ niveles inferior necesita esos datos. Aquí hay un ejemplo de Twitter, con todos los avatares resaltados:

Datos de usuarios de Twitter

Vamos a fingir que estamos en el nivel superior App El componente contiene el user objeto en estado. El user contiene el avatar, el identificador y otra información del perfil del usuario actual.

Para poder entregar el user datos para los 3 Avatar componentes, el user Debe estar entretejido a través de un conjunto de componentes intermedios que no necesitan datos.

Envío de los datos del usuario a los componentes del Avatar

Obtener los datos allí es como enhebrar una aguja en una expedición minera. Espera, eso no tiene ningún sentido. De todos modos, Es un dolor. También conocido como “perforación de hélice”.

Lo más importante es que no es un muy buen diseño de software. Los componentes intermedios se ven obligados a aceptar y transmitir accesorios que no les importan. Esto significa que refactorizar y reutilizar esos componentes será más difícil de lo necesario.

¿No sería bueno si los componentes que no necesitan los datos no tuvieran que verlos en absoluto?

Redux es una forma de resolver este problema.

Pasar datos entre componentes adyacentes

Si tienes componentes hermanos y necesitas compartir datos, la forma de hacerlo en React es extraer esos datos arriba en un componente principal y pasarlo con accesorios.

Aunque eso puede resultar engorroso. Redux puede ayudarte brindándote un global “padre” donde puedes almacenar los datos y luego puedes connect los componentes hermanos de los datos con React-Redux.

Utilice React-Redux para conectar datos a cualquier componente

Usando el connect función que viene con react-redux, puedes conectar cualquier componente a la tienda de Redux y extraer los datos que necesita.

Conexión de Redux a los componentes de Avatar

Redux también hace otras cosas interesantes, como facilitar la depuración (Redux DevTools te permite inspeccionar cada cambio de estado) y depurar viajes en el tiempo (puedes retroceder cambia de estado y mira cómo se veía tu aplicación en el pasado), y puede hacer que tu código sea más mantenible a largo plazo. También te enseñará más sobre programación funcional.

Alternativas integradas de Redux

Si Redux parece excesivo para su situación, eche un vistazo a estas alternativas. Están integrados directamente en React.

Alternativa de Redux: la API de contexto de React

En segundo plano, React-Redux utiliza la API de contexto incorporada de React para pasar datos. Si lo deseas, puedes eliminar al intermediario y usar Context directamente. Te perderás las buenas características de Redux mencionadas anteriormente, pero si tu aplicación es simple y quieres una forma fácil de pasar datos, Context podría ser perfecto.

Ya que estás aquí, asumiré que quieres aprender Redux, y no lo haré Comparar Redux con la API de contexto o el useContext y useReducer Ganchos aquí mismo. Puedes obtener más información en esos enlaces.

Si quieres profundizar en la API de contexto, mira mi curso Contexto de React para la gestión estatal en egghead.

Otra alternativa: utilizar el children Prop

Dependiendo de cómo estructures tu aplicación, es posible que puedas pasar datos a los componentes secundarios de manera más directa usando una combinación de children accesorio y otros accesorios como “ranuras”. Puedes omitir efectivamente algunos niveles en la jerarquía si lo organizas correctamente.

Tengo un artículo sobre esto “patrón de ranuras” y cómo organizar su árbol de componentes para pasar datos de manera más eficiente.

Aprenda Redux, comenzando con Plain React

Vamos a adoptar un enfoque incremental, comenzando con una aplicación React simple con estado de componente, agregando partes de Redux pieza por pieza y solucionando los errores a lo largo del camino. Llamémoslo “Desarrollo basado en errores” 🙂

Aquí hay un contador:

Contracomponente

En este ejemplo, el componente Contador contiene el estado y la aplicación que lo rodea es un contenedor simple.Contador.js

import React from 'react';

class Counter extends React.Component {
  state = { count: 0 }

  increment = () => {
    this.setState({
      count: this.state.count + 1
    });
  }

  decrement = () => {
    this.setState({
      count: this.state.count - 1
    });
  }

  render() {
    return (
      <div>
        <h2>Counter</h2>
        <div>
          <button onClick={this.decrement}>-</button>
          <span>{this.state.count}</span>
          <button onClick={this.increment}>+</button>
        </div>
      </div>
    )
  }
}

export default Counter;

A modo de repaso rápido, así es como funciona esto:

  • El count El estado se almacena en el Counter componente
  • Cuando el usuario hace clic en “+”, el botón onClick se llama al controlador, que llama al increment función.
  • El increment La función actualiza el estado con el nuevo recuento.
  • Debido a que se cambió el estado, React vuelve a renderizar el Counter componente (y sus hijos), y se muestra el nuevo valor del contador.

Si necesita más detalles sobre cómo funcionan los cambios de estado, lea Una guía visual del estado en React y luego vuelve aquí.

¡sigue adelante!

¡La mejor manera de aprender realmente estas cosas es probándolas! Aquí tienes un CodeSandbox donde puedes seguirlo:

–> Abra este CodeSandbox en una pestaña separada

Te recomiendo encarecidamente que mantengas CodeSandbox sincronizado con el tutorial y escribas los ejemplos a medida que avanzas.

Agregue Redux a la aplicación React

En CodeSandbox, expanda la sección Dependencias en el panel izquierdo y haga clic en Agregar dependencia.

Buscar redux, agréguelo, luego haga clic en Agregar dependencia nuevamente y busque react-redux y agrégalo.

En un proyecto local, puedes instalarlos con Yarn o NPM: npm install --save redux react-redux.

redux frente a react-redux

reduxle brinda una tienda y le permite mantener el estado en ella, sacarlo y responder cuando el estado cambia. Pero eso es todo lo que hace.

En realidad lo es react-redux que te permite conectar partes del estado a componentes de React.

Así es: redux no sabe nada sobre React en absoluto.

Pero estas bibliotecas son como dos guisantes en una vaina. El 99,999% de las veces, cuando alguien menciona “Redux” en el contexto de React, se refiere a ambas bibliotecas en conjunto. Así que tenlo en cuenta cuando veas que Redux se menciona en StackOverflow, Reddit o en otro lugar.

El redux La biblioteca también se puede utilizar fuera de una aplicación React. Funcionará con Vue, Angular e incluso con aplicaciones backend Node/Express.

Redux tiene una tienda global

Comenzaremos analizando Redux por sí solo y solo una parte: el tienda.

Hemos hablado de cómo Redux mantiene el estado de tu aplicación en una sola tienda. Y cómo puedes extraer partes de ese estado y conectarlas a tus componentes como accesorios.

A menudo verás que las palabras “estado” y “tienda” se usan indistintamente. Técnicamente, el estado son los datos, y el tienda Es donde se guarda.

Entonces: como paso 1 de nuestra refactorización de React simple a Redux, necesitamos crear una tienda para almacenar el estado.

Crea la tienda Redux

Redux viene con una función útil que crea tiendas y se llama createStore. Es bastante lógico, ¿no?

En index.js, hagamos una tienda. Importar createStore y llámalo así:índice.js

import { createStore } from 'redux';

const store = createStore();

const App = () => (
  <div>
    <Counter/>
  </div>
);

Esto debería fallar con el error “Se esperaba que el reductor fuera una función”

Error: Se esperaba que el reductor fuera una función.

La tienda necesita un reductor

Entonces, esto es lo que pasa con Redux: no es muy inteligente.

Se podría esperar que al crear una tienda, le daría a su estado un buen valor predeterminado. ¿Quizás un objeto vacío, quizás?

Pero no. Aquí no hay convención sobre configuración.

Redux hace cero suposiciones sobre la forma de su estado. Podría ser un objeto, un número, una cadena o lo que necesites. ¡Depende de ti!

Tenemos que proporcionar una función que devuelva el estado. Esa función se llama a reductor (veremos por qué en un minuto). Así que hagamos uno realmente simple, pasémoslo a createStore, y mira lo que pasa:índice.js

function reducer(state, action) {
  console.log('reducer', state, action);
  return state;
}

const store = createStore(reducer);

Después de realizar este cambio, abra la consola (en CodeSandbox, haga clic en el botón Consola en la parte inferior).

Deberías ver un mensaje registrado allí, algo como esto:

reductor objeto indefinido { tipo: @@redux/INIT }

(Redux aleatoriza las letras y números después de INIT)

Observe cómo Redux llamó a su reductor en el momento en que creó la tienda. (Para demostrarlo: poner un console.log inmediatamente después de la llamada a createStore y observe cómo se imprime después del reductor)

Observe también cómo Redux pasó un state de undefined, y la acción era un objeto con un type propiedad.

Hablaremos más sobre las acciones en un minuto. Por ahora, repasemos el reductor.

¿Qué es un reductor Redux?

El término “reductor” puede parecer un poco aterrador y extraño, pero después de esta sección creo que llegarás a estar de acuerdo en que es, como dice el refrán, “solo una función”

¿Alguna vez has usado el reduce ¿función en una matriz?

Así es como funciona: le pasas una función y llama a tu función una vez para cada elemento de la matriz, de manera similar a cómo map obras – con las que probablemente estés familiarizado map a partir de la representación de listas de cosas en React.

Su función se llama con 2 argumentos: el resultado de la última iteración y el elemento de matriz actual. Combina el elemento actual con el resultado anterior “total” y devuelve el nuevo total.

Esto tendrá más sentido con un ejemplo:

var letters = ['r', 'e', 'd', 'u', 'x'];

// `reduce` takes 2 arguments:
//   - a function to do the reducing (you might say, a "reducer")
//   - an initial value for accumulatedResult
var word = letters.reduce(
  function(accumulatedResult, arrayItem) {
    return accumulatedResult + arrayItem;
  },
''); // <-- notice this empty string argument: it's the initial value

console.log(word) // => "redux"

La función a la que pasas reduce Podría llamarse legítimamente un “reductor”… porque reduce toda una serie de elementos hasta un único resultado.

Redux es básicamente una versión elegante de Array reduce. Anteriormente viste cómo los reductores Redux tienen esta firma:

(state, action) => newState

Significado: toma la corriente state, y un action, y devuelve el newState. Se parece mucho a la firma de un Array.reduce ¡reductor!

(accumulatedValue, nextItem) => nextAccumulatedValue

¡Los reductores Redux funcionan igual que la función que pasas a Array.reduce! 🙂 Lo que reducen son las acciones. Ellos reducir un conjunto de acciones (a lo largo del tiempo) a un solo estado. La diferencia es que con la reducción de Array sucede todo a la vez, y con Redux, sucede durante la vida útil de la aplicación en ejecución.

Si aún no estás muy seguro, consulta mi guía Cómo funcionan los reductores Redux. De lo contrario, sigamos adelante.

Dale al reductor un estado inicial

Recuerde que el trabajo del reductor es tomar la corriente state y un action y devolver el nuevo estado.

También tiene otro trabajo: debería devolver el estado inicial la primera vez que se llama. Esto es como “arrancar” tu aplicación. Tiene que empezar por algún lado, ¿no?

La forma idiomática de hacerlo es definir un initialState variable y utilice la sintaxis de argumento predeterminada de ES6 para asignarla state.

Ya que vamos a mover nuestro Counter estado en Redux, configuremos su estado inicial ahora mismo. Dentro del Counter componente nuestro estado se representa como un objeto con a count, así que reflejaremos esa misma forma aquí.índice.js

const initialState = {
  count: 0
};

function reducer(state = initialState, action) {
  console.log('reducer', state, action);
  return state;
}

Si vuelves a mirar la consola, la verás impresa {count: 0} como el valor para state. Eso es lo que queremos.

Esto nos lleva a una regla importante sobre los reductores.

Regla importante de los reductores #1: Nunca regrese indefinido desde un reductor.

Siempre quieres que tu estado esté definido. Un estado definido es un estado feliz. An unEl estado definido es unfeliz (y probablemente romperá tu aplicación).

Acciones de envío para cambiar el estado

Sí, dos términos nuevos a la vez: vamos a “enviar” algunas “acciones”

¿Qué es una acción Redux?

An acción es el lenguaje Redux para un objeto simple con una propiedad llamada type. Eso es prácticamente todo. Siguiendo esas 2 reglas, esta es una acción:

{
  type: "add an item",
  item: "Apple"
}

Esto también es una acción:

{
  type: 7008
}

Aquí hay otro:

{
  type: "INCREMENT"
}

Las acciones son cosas de forma muy libre. Siempre que sea un objeto con a type Es un juego limpio.

Para mantener las cosas sensatas y mantenibles, los usuarios de Redux generalmente damos a nuestras acciones tipos que son cadenas simples, y a menudo en mayúsculas, para indicar que deben ser valores constantes.

Un objeto de acción describe un cambio que desea realizar (como “incremente el contador”) o un evento que sucede (como “la solicitud al servidor falló con este error”).

Las acciones, a pesar de su nombre que suena activo, son objetos aburridos e inertes. En realidad no lo hacen do cualquier cosa. Al menos no por sí solos.

Para que una acción HAGA algo, necesitas: despacho it.

Cómo funciona Redux Dispatch

La tienda que creamos anteriormente tiene una función incorporada llamada dispatch. Llámalo con una acción y Redux llamará a tu reductor con esa acción (y luego reemplazará el estado con lo que haya devuelto tu reductor).

Vamos a probarlo con nuestra tienda.índice.js

const store = createStore(reducer);
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "INCREMENT" });
store.dispatch({ type: "DECREMENT" });
store.dispatch({ type: "RESET" });

Agregue esas llamadas de envío a su CodeSandbox y verifique la consola.

reductor objeto indefinido { tipo: @@redux/INIT }

Cada llamada a dispatch ¡Resulta en una llamada a tu reductor!

¿Observa también cómo el estado es el mismo cada vez? {count: 0} nunca cambia.

Eso es porque nuestro reductor no lo es actuando sobre esas acciones. Aunque es una solución fácil. Hagámoslo ahora.

Manejar acciones en el reductor Redux

Para que las acciones realmente hagan algo, necesitamos escribir algún código en el reductor que inspeccione el type de cada acción y actualizar el estado en consecuencia.

Hay algunas formas de hacer esto.

Podrías crear un objeto elegante donde busques una función de controlador por el tipo de acción…

O podrías escribir un montón de declaraciones if/else…

if(action.type === "INCREMENT") {
  ...
} else if(action.type === "RESET") {
  ...
}

O podrías usar un simple switch declaración, que es lo que mostraré a continuación porque es sencilla y una forma muy común de hacerlo.

Algunas personas odian el switch aunque. Si ese es tu caso – no dudes en escribir tus reductores como quieras 🙂

Así es como manejaremos las acciones:índice.js

function reducer(state = initialState, action) {
  console.log('reducer', state, action);

  switch(action.type) {
    case 'INCREMENT':
      return {
        count: state.count + 1
      };
    case 'DECREMENT':
      return {
        count: state.count - 1
      };
    case 'RESET':
      return {
        count: 0
      };
    default:
      return state;
  }
}

Prueba esto y echa un vistazo a la consola.

reductor objeto indefinido { tipo: @@redux/INIT }

¡Oye, mira eso! El count ¡está cambiando!

Estamos listos para conectar esto a React, pero hablemos de este código reductor por un segundo.

Cómo mantener puros sus reductores

Otra regla sobre los reductores es que deben serlo funciones puras. Esto significa que no pueden modificar sus argumentos y no pueden tener efectos secundarios.

Regla reductora n.° 2: Los reductores deben ser funciones puras.

A “efecto secundario” es cualquier cambio en algo fuera del alcance de la función. No cambie variables fuera del alcance de la función, no llame a otras funciones que cambien cosas (como fetch, que afecta a la red y otros sistemas), no enviar acciones, etc.

Técnicamente console.log es un efecto secundario, pero lo permitiremos.

Lo más importante es esto: no modifiques el state argumento.

Esto significa que no puedes hacerlo state.count = 0 o state.items.push(newItem) o state.count++, o cualquier otro tipo de mutación – no state en sí mismo, y no a ninguna de las subpropiedades de state.

Piénsalo como un juego donde lo único que puedes hacer es return { ... }. Es un juego divertido. Enloquecedor al principio. Pero mejorarás en ello con la práctica.

He elaborado una guía completa para Cómo realizar actualizaciones inmutables en Redux, mostrando 7 patrones comunes para actualizar el estado dentro de objetos y matrices.

Otra gran opción es instalar el Sumergir biblioteca y úsela en sus reductores. Immer le permite escribir código regular de aspecto mutable y produce actualizaciones inmutables automáticamente. Aprenda a utilizar Immer aquí.

Mi consejo: si estás iniciando una aplicación nueva, usa Immer desde el principio. Te ahorrará muchos problemas. Pero te estoy mostrando el camino “difícil” porque así es como todavía lo hace mucho código, y seguramente verás reductores escritos sin Immer.

Todas estas reglas…

Devuelve siempre un estado, nunca cambies de estado, no conectes todos los componentes, come brócoli, no te quedes fuera más allá de las 11… es agotador. Es como una fábrica de reglas y ni siquiera sé qué es eso.

Sí, Redux puede ser como un padre autoritario. Pero viene de un lugar de amor. Amor por la programación funcional.

Redux se basa en la idea de inmutabilidad, porque mutar el estado global es el camino a la ruina.

¿Alguna vez has intentado mantener tu estado en un objeto global? Funciona muy bien al principio. Agradable y fácil. Todo puede acceder al estado porque siempre está disponible y realizar cambios es sencillo.

Y luego el estado comienza a cambiar de manera impredecible y se vuelve imposible encontrar el código que lo está cambiando.

Redux evita estos problemas con algunas reglas simples.

  • El estado es de sólo lectura y las acciones son la única forma de modificarlo.
  • Los cambios ocurren de una manera y solo de una manera: dispatch(action) -> reducer -> new state.
  • La función reductora debe ser “pura” – no puede modificar sus argumentos y no puede tener efectos secundarios.

Cómo utilizar Redux con React

En este punto tenemos un pequeño encantador store con a reducer que sabe cómo actualizar el state cuando recibe un action.

Ahora es el momento de conectar Redux a React.

Para ello, el react-redux La biblioteca viene con 2 cosas: un componente llamado Provider, y una función llamada connect.

Envolviendo toda la aplicación con el Provider componente, cada componente en el árbol de aplicaciones podrá acceder a la tienda Redux si así lo desea.

En index.js, importar el Provider y envolver el contenido de App con él. Pasa el store como accesorio.índice.js

import { Provider } from 'react-redux';

...

const App = () => (
  <Provider store={store}>
    <Counter/>
  </Provider>
);

Después de esto, Counter, y los hijos de Counter, e hijos de sus hijos, etc. – todos ellos ahora pueden acceder a la tienda Redux.

Pero no automáticamente. Necesitaremos usar el connect función en nuestros componentes para acceder a la tienda.

Cómo funciona el proveedor React-Redux

Esto Provider La cosa podría parecer magia total. Es un poco; en realidad usa React Característica de contexto bajo el capó.

El contexto es como un pasaje secreto conectado a cada componente y que utiliza connect abre la puerta del pasillo.

Imagínate verter jarabe sobre una pila de panqueques y cómo logra llegar a TODOS los panqueques aunque solo lo hayas vertido sobre el de arriba. Provider hace eso por Redux.

Prepare el componente contador para Redux

En este momento el Contador tiene estado local. Vamos a arrancarlo, en preparación para conseguirlo count como accesorio de Redux.

Elimine la inicialización del estado en la parte superior y la setState llamadas internas increment y decrement. Luego, reemplace this.state.count con this.props.count.Contador.js

class Counter extends React.Component {
  // state = { count: 0 }; // remove this

  increment = () => {
    /*
    // Remove this
    this.setState({
      count: this.state.count + 1
    });
    */
  };

  decrement = () => {
    /*
    // Also remove this
    this.setState({
      count: this.state.count - 1
    });
    */
  };

  render() {
    return (
      <div className="counter">
        <h2>Counter</h2>
        <div>
          <button onClick={this.decrement}>-</button>
          <span className="count">{
            // Replace state:
            //// this.state.count
            // With props:
            this.props.count
          }</span>
          <button onClick={this.increment}>+</button>
        </div>
      </div>
    );
  }
}

Esto se irá increment y decrement vacío. Los completaremos nuevamente pronto.

También notarás que el conteo ha desaparecido – lo cual debería ser así, porque nada pasa a un count prop a Counter yet.

Conecte el componente a Redux

Para conseguir el count Desde Redux, primero debemos importar el connect función en la parte superior de Counter.js:Contador.js

import { connect } from 'react-redux';

Luego necesitamos “conectar” el componente Counter a Redux en la parte inferior:Contador.js

// Add this function:
function mapStateToProps(state) {
  return {
    count: state.count
  };
}

// Then replace this:
// export default Counter;

// With this:
export default connect(mapStateToProps)(Counter);

Anteriormente exportábamos el componente en sí. Ahora lo estamos envolviendo con esto connect llamada de función, por lo que estamos exportando el conectado Contador. En lo que respecta al resto de tu aplicación, este parece un componente normal.

¡Y el conde debería reaparecer! Excepto que esté congelado hasta que volvamos a implementar el incremento/decremento.

Cómo utilizar React Redux connect

Quizás notes que la llamada parece pequeña… rara. Por qué connect(mapStateToProps)(Counter) y no connect(mapStateToProps, Counter) o connect(Counter, mapStateToProps)? ¿Qué está haciendo eso?

Está escrito de esta manera porque connect es un función de orden superior, que es una forma elegante de decir que devuelve una función cuando la llamas. Y luego llamando eso La función con un componente devuelve un nuevo componente (envuelto).

Otro nombre para esto es a componente de orden superior (también conocido como “HOC”). Los HOC han recibido mala prensa en el pasado, pero sigue siendo un patrón bastante útil connect es un buen ejemplo de uno útil.

Qué connect lo que hace es conectarse a Redux, extraer todo el estado y pasarlo a través del mapStateToProps función que usted proporciona. Esta debe ser una función personalizada porque solo you conoce la “forma” del estado que has almacenado en Redux.

Cómo funciona mapStateToProps

connect pasa todo el estado a tu mapStateToProps funciona como si dijera: “Oye, dime qué necesitas de este lío confuso”

El objeto del que regresas mapStateToProps se introduce en su componente como accesorios. El ejemplo anterior pasará state.count como el valor del count prop: las claves del objeto se convierten en nombres de prop y sus valores correspondientes se convierten en valores props’. Así que verás, esta función literalmente define un mapeo del estado a las propiedades.

Por cierto – el nombre mapStateToProps Es convencional, pero no es especial en ningún sentido. Puedes acortarlo a mapState o llámalo como quieras. Mientras sea necesario el state objeto y devuelve un objeto lleno de accesorios, estás bien.

¿Por qué no aprobar todo el estado?

En el ejemplo anterior, nuestro estado es ya en la forma correcta… y parece que tal vez mapDispatchToProps es innecesario. Si esencialmente copia el argumento (estado) en un objeto que es idéntico al estado, ¿de qué sirve?

En ejemplos muy pequeños, eso podría ser todo lo que hace, pero generalmente seleccionará fragmentos de datos que el componente necesita de una colección más grande de estados.

Y además, sin el mapStateToProps función, connect no pasará ningún dato de estado.

Tú could pase todo el estado y deje que el componente lo solucione. Sin embargo, no es un gran hábito, porque el componente necesitará conocer la forma del estado Redux para elegir lo que necesita, y será más difícil cambiar esa forma más adelante, si es necesario.

Enviar acciones de Redux desde un componente React

Ahora que nuestro Contador es connected, tenemos el count valor. Ahora bien, ¿cómo podemos enviar acciones para cambiar el recuento?

Bueno, connect te respalda: además de pasar en el estado (mapeado), también pasa en el dispatch ¡Función desde la tienda!

Para enviar una acción desde el interior del Contador, podemos llamar this.props.dispatch con una acción.

Nuestro reductor ya está configurado para manejar el INCREMENT y DECREMENT acciones, así que despachémoslas desde incremento/decremento:Contador.js

increment = () => {
  this.props.dispatch({ type: "INCREMENT" });
};

decrement = () => {
  this.props.dispatch({ type: "DECREMENT" });
};

Y ahora hemos terminado. Los botones deberían volver a funcionar.

¡Prueba esto! Agregar un botón de reinicio

Aquí hay un pequeño ejercicio para probar: agregue un botón “Reset” al contador que envía la acción “RESET” cuando se hace clic.

El reductor ya está configurado para manejar esta acción, por lo que solo deberías necesitar modificar Counter.js.

Constantes de acción

En la mayoría de las aplicaciones Redux, verás constantes de acción Se utiliza en lugar de cuerdas simples. Es un nivel adicional de abstracción que puede ahorrarle algo de tiempo a largo plazo.

Las constantes de acción ayudan a evitar errores tipográficos, y los errores tipográficos en los nombres de las acciones pueden ser una gran molestia: ¿no hay errores, no hay señales visibles de que algo esté roto y sus acciones no parecen estar haciendo nada? Podría ser un error tipográfico.

Las constantes de acción son fáciles de escribir: almacene sus cadenas de acción en variables.

Un buen lugar para ponerlos es en un actions.js archivo (cuando tu aplicación es pequeña, de todos modos).acciones.js

export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";

Luego puedes importar los nombres de las acciones y usarlos en lugar de escribir las cadenas:Contador.js

import React from "react";
import { INCREMENT, DECREMENT } from './actions';

class Counter extends React.Component {
  state = { count: 0 };

  increment = () => {
    this.props.dispatch({ type: INCREMENT });
  };

  decrement = () => {
    this.props.dispatch({ type: DECREMENT });
  };

  render() {
    ...
  }
}

¿Qué es un creador de acciones Redux?

Hasta ahora hemos estado escribiendo objetos de acción manualmente. Como los paganos.

¿Qué pasaría si tuvieras un función ¿Eso los escribiría para ti? ¡No más acciones mal escritas!

Puedo decirte que piensas que esto es una locura. Qué difícil es escribir { type: INCREMENT } ¿sin equivocarse?

A medida que su aplicación crece y tiene más de 2 acciones, esas acciones comienzan a volverse más complejas – pasando más datos que solo un type – Puede ser útil tener creadores de acción.

Al igual que las constantes de acción, no son una requisito aunque. Esta es otra capa de abstracción y si no quieres molestarte con ella en tu aplicación, está bien.

De todas formas, te explicaré qué son. Puedes decidir si quieres usarlos a veces/siempre/nunca.

An creador de acción En términos de Redux, es un término elegante para una función que devuelve un objeto de acción. Eso es todo 🙂

Aquí hay dos de ellos, que devuelven acciones familiares. Estos van muy bien actions.js junto a las constantes de acción, por cierto.acciones.js

export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";

export function increment() {
  return { type: INCREMENT };
}

export const decrement = () => ({ type: DECREMENT });

Los escribí de dos maneras diferentes – como function y como flecha – para mostrar que no importa cómo los escribas. Elige tu favorito y déjate llevar.

Notarás que los nombres de las funciones son camelCase (bueno, lo serían ifTheyWereLonger) mientras que las constantes de acción son UPPER_CASE_WITH_UNDERSCORES. Esto también es sólo una convención. Te ayuda a saber si estás viendo una función creadora de acciones o una constante de acción. Pero siéntete libre de nombrar el tuyo como quieras. A Redux no le importa.

Ahora… ¿qué haces con un creador de acción? ¡Impórtalo y envíalo, por supuesto!Contador.js

import React from "react";
import { increment, decrement } from './actions';

class Counter extends React.Component {
  state = { count: 0 };

  increment = () => {
    this.props.dispatch(increment()); // << use it here
  };

  decrement = () => {
    this.props.dispatch(decrement());
  };

  render() {
    ...
  }
}

Lo fundamental es recordar llama al creador de acciones()!

No dispatch(increment) 🚫

Do dispatch(increment()) ✅

Recuerde que un creador de acciones es una función simple y antigua. Dispatch quiere una acción objeto, no es una función.

Además: es casi seguro que arruinarás esto y quedarás muy confundido. Al menos una vez, probablemente muchas veces. Eso es normal. Yo todavía Olvídate a veces.

Cómo utilizar React Redux mapDispatchToProps

Ahora que ya sabes qué es un creador de acción, podemos hablar de uno más nivel de abstracción. (Lo sé. LO SÉ. Aunque es opcional.)

Ya sabes cómo connect pasa en un dispatch ¿función? Y sabes cómo te cansas mucho de escribir this.props.dispatch ¿Todo el tiempo y te molesta lo desordenado que se ve? (ve conmigo aquí)

Escribiendo un mapDispatchToProps ¡objeto (o función! pero normalmente objeto) y pasándolo a connect Cuando envuelvas tu componente, recibirás esos creadores de acciones como accesorios invocables. Esto es lo que quiero decir:Contador.js

import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './actions';

class Counter extends React.Component {
  increment = () => {
    // We can call the `increment` prop,
    // and it will dispatch the action:
    this.props.increment();
  }

  decrement = () => {
    this.props.decrement();
  }

  render() {
    // ...
  }
}

function mapStateToProps(state) {
  return {
    count: state.count
  };
}

// in this object, keys become prop names,
// and values should be action creator functions.
// They get bound to `dispatch`.
const mapDispatchToProps = {
  increment,
  decrement
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

Esto es bueno porque te ahorra tener que llamar dispatch manualmente.

También puedes escribir mapDispatch como función, pero el objeto cubre probablemente el 95% de lo que necesitas. Puedes leer más sobre el Formulario mapDispatch funcional y por qué probablemente no lo necesites.

Cómo obtener datos con Redux Thunk

Dado que se supone que los reductores son “puros”, no podemos realizar ninguna llamada API ni enviar acciones desde dentro de un reductor.

¡Tampoco podemos hacer esas cosas dentro de un creador de acción simple!

Pero ¿qué pasaría si pudiéramos crear un creador de acción retorno ¿una función que podría hacer nuestro trabajo? Algo como esto:

function getUser() {
  return function() {
    return fetch('/current_user');
  };
}

De fábrica, Redux no admite acciones como esta. Stock Redux solo acepta objetos simples como acciones.

Aquí es donde entra en juego el redux-thunk. Es un middleware, básicamente un complemento para Redux, que permite a Redux manejar acciones como getUser(), arriba.

Puedes enviar estas “acciones thunk” como cualquier otro creador de acciones: dispatch(getUser()).

¿Qué es un “thunk”?

A “thunk” es un nombre (poco común) para a función eso lo devuelve otra función.

En términos de Redux, es un creador de acciones que devuelve una función en lugar de un objeto de acción simple, como este:

function doStuff() {
  return function(dispatch, getState) {
    // dispatch actions here
    // or fetch data
    // or whatever
  }
}

Si desea obtener información técnica, la función que se devuelve es “thunk” y la que la devuelve es “action creator”. Generalmente llamo a todo el paquete una acción “thunk.”

A la función que devuelvas de tu creador de acciones se le pasarán 2 argumentos: el dispatch función, y getState.

La mayoría de las veces solo lo necesitarás dispatch, pero a veces quieres hacer algo condicionalmente, en función de algún valor en el estado Redux. En ese caso, llama getState() y tendrás todo el estado para leer según sea necesario.

Cómo configurar Redux Thunk

Para instalar redux-thunk con NPM o Yarn, ejecute npm install --save redux-thunk.

Luego, en index.js (o donde sea que crees tu tienda), importa redux-thunk y aplicarlo a la tienda con Redux applyMiddleware función:

import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';

function reducer(state, action) {
  // ...
}

const store = createStore(
  reducer,
  applyMiddleware(thunk)
);

Sólo asegúrate de envolver thunk en el applyMiddlware llama o no funcionará. No pases thunk directamente.

Un ejemplo de obtención de datos con Redux

Imaginemos que quieres mostrar una lista de productos. Tienes una API de backend que responde a GET /products, entonces creas esta acción thunk para realizar la búsqueda:ProductActions.js

export function fetchProducts() {
  return dispatch => {
    dispatch(fetchProductsBegin());
    return fetch("/products")
      .then(res => res.json())
      .then(json => {
        dispatch(fetchProductsSuccess(json.products));
        return json.products;
      })
      .catch(error => dispatch(fetchProductsFailure(error)));
  };
}

El fetch("/products") Parte es lo que realmente obtiene los datos. Luego tenemos algunas llamadas a dispatch Antes y después.

Envíe la acción para obtener los datos

Para iniciar la llamada y obtener los datos, necesitamos enviar el fetchProducts acción.

¿Dónde deberías hacerlo?

Si un componente en particular necesita los datos, el mejor lugar para iniciar la búsqueda suele ser el correcto después ese componente se monta, en su componentDidMount método de ciclo de vida.

O, si estás usando Hooks, dentro del gancho useEffect hay un buen lugar.

A veces eres realmente atractivo global datos que toda la aplicación necesita – pensar “perfil de usuario” o “traducciones i18n”. En esos casos, envíe la acción inmediatamente después de crear la tienda, con store.dispatch, en lugar de esperar a que se monte un componente.

Cómo nombrar tus acciones de Redux

Las acciones de Redux que obtienen datos generalmente vienen en tripletes: INICIO, ÉXITO, FRACASO. Esto no es un requisito, es sólo una convención.

Este patrón INICIO/ÉXITO/FRACASO es bueno porque te brinda ganchos para realizar un seguimiento de lo que está sucediendo –por ejemplo, configurando un indicador “de carga” true en respuesta a la acción BEGIN, y luego false después de ÉXITO o FRACASO.

Y, como ocurre con casi todo lo demás en Redux… esta también es una convención que puedes ignorar si no la necesitas.

Antes inicias la llamada API y envías la acción BEGIN.

Entonces después la llamada tiene éxito, envías ÉXITO con los datos. Si en cambio falló, envía FAILURE con el error.

A veces el último se llama ERROR. En realidad no importa cómo lo llames, siempre y cuando seas coherente al respecto.

Cuidado: Enviar una acción de ERROR y manejar una FALLA provocará un tirón de pelo sin fin a medida que rastrea su código, dándose cuenta de que la acción se envía correctamente pero los datos nunca se actualizan. Aprende de mis errores 🙂

Así es como se ven esas acciones, junto con los creadores de acciones para ellas:ProductActions.js

export const FETCH_PRODUCTS_BEGIN   = 'FETCH_PRODUCTS_BEGIN';
export const FETCH_PRODUCTS_SUCCESS = 'FETCH_PRODUCTS_SUCCESS';
export const FETCH_PRODUCTS_FAILURE = 'FETCH_PRODUCTS_FAILURE';

export const fetchProductsBegin = () => ({
  type: FETCH_PRODUCTS_BEGIN
});

export const fetchProductsSuccess = products => ({
  type: FETCH_PRODUCTS_SUCCESS,
  payload: { products }
});

export const fetchProductsFailure = error => ({
  type: FETCH_PRODUCTS_FAILURE,
  payload: { error }
});

Escribiremos un reductor para guardar los productos en la tienda Redux cuando reciba el FETCH_PRODUCTS_SUCCESS acción. También establecerá un loading marque como verdadero cuando comience la búsqueda y como falso cuando finalice o falle.productReducer.js

import {
  FETCH_PRODUCTS_BEGIN,
  FETCH_PRODUCTS_SUCCESS,
  FETCH_PRODUCTS_FAILURE
} from './productActions';

const initialState = {
  items: [],
  loading: false,
  error: null
};

export default function productReducer(state = initialState, action) {
  switch(action.type) {
    case FETCH_PRODUCTS_BEGIN:
      // Mark the state as "loading" so we can show a spinner or something
      // Also, reset any errors. We're starting fresh.
      return {
        ...state,
        loading: true,
        error: null
      };

    case FETCH_PRODUCTS_SUCCESS:
      // All done: set loading "false".
      // Also, replace the items with the ones from the server
      return {
        ...state,
        loading: false,
        items: action.payload.products
      };

    case FETCH_PRODUCTS_FAILURE:
      // The request failed. It's done. So set loading to "false".
      // Save the error, so we can display it somewhere.
      // Since it failed, we don't have items to display anymore, so set `items` empty.
      //
      // This is all up to you and your app though:
      // maybe you want to keep the items around!
      // Do whatever seems right for your use case.
      return {
        ...state,
        loading: false,
        error: action.payload.error,
        items: []
      };

    default:
      // ALWAYS have a default case in a reducer
      return state;
  }
}

Por último, necesitamos pasar los productos a un ProductList componente que los mostrará y también será responsable de iniciar la obtención de datos.Lista de productos.js

import React from "react";
import { connect } from "react-redux";
import { fetchProducts } from "/productActions";

class ProductList extends React.Component {
  componentDidMount() {
    this.props.dispatch(fetchProducts());
  }

  render() {
    const { error, loading, products } = this.props;

    if (error) {
      return <div>Error! {error.message}</div>;
    }

    if (loading) {
      return <div>Loading...</div>;
    }

    return (
      <ul>
        {products.map(product =>
          <li key={product.id}>{product.name}</li>
        )}
      </ul>
    );
  }
}

const mapStateToProps = state => ({
  products: state.products.items,
  loading: state.products.loading,
  error: state.products.error
});

export default connect(mapStateToProps)(ProductList);

Me refiero a los datos con state.products.<whatever> en lugar de simplemente state.<whatever> porque supongo que probablemente tendrás más de un reductor, cada uno manejando su propia porción de estado. Para que esto funcione, podemos escribir un rootReducer.js archivo que los reúne todos:rootReducer.js

import { combineReducers } from "redux";
import products from "./productReducer";

export default combineReducers({
  products
});

Luego, cuando creamos nuestra tienda, podemos pasar este reductor “root”:índice.js

import rootReducer from './rootReducer';

// ...

const store = createStore(rootReducer);

Manejo de errores en Redux

El manejo de errores aquí es bastante ligero, pero la estructura básica será la misma para la mayoría de las acciones que realizan llamadas API. La idea general es:

  1. Envíe una acción de FALLA cuando la llamada falle
  2. Maneje esa acción de FALLA en el reductor configurando algún tipo de indicador y/o guardando el mensaje de error.
  3. Pase el indicador de error y el mensaje (si tiene uno) a los componentes que necesitan manejar errores y represente condicionalmente el error como crea conveniente.

¿Puedes evitar el doble renderizado?

Esta es una preocupación muy común. Y sí, eso will renderizar más de una vez.

Se renderizará en un estado vacío, luego se volverá a renderizar en un estado de carga y luego se volverá a renderizar otra vez con productos para mostrar. ¡El horror! 3 renders! (podrías reducirlo a 2 si saltas directamente al estado “cargando”)

Es posible que le preocupen las representaciones innecesarias debido al rendimiento, pero no lo haga: las representaciones individuales son muy rápidas. Si estás trabajando en una aplicación en la que son lo suficientemente lentos como para darse cuenta, haz algunos perfiles y descubre por qué es así.

Piénsalo de esta manera: la aplicación necesita mostrarse algo cuando no hay productos, o cuando se están cargando, o cuando hay un error. Probablemente no quieras simplemente mostrar una pantalla en blanco hasta que los datos estén listos. Esto le brinda la oportunidad de hacer que esa experiencia de usuario brille.

¿Qué sigue?

¡Esperamos que este tutorial te haya ayudado a entender mejor Redux!

FUENTE

screen tagSoporte