Cómo comenzar con Redux para la administración del estado de JavaScript – CloudSavvy IT

Logotipo de Redux

Redux es una herramienta de administración de estado, creada específicamente para aplicaciones JavaScript del lado del cliente que dependen en gran medida de datos complejos y API externas, y proporciona excelentes herramientas para desarrolladores que facilitan el trabajo con sus datos.

¿Qué hace Redux?

En pocas palabras, Redux es un almacén de datos centralizado. Todos los datos de su aplicación se almacenan en un objeto grande. los Herramientas de desarrollo de Redux haz esto fácil de visualizar:

Un almacén de datos de Redux visualizado

Este estado es inmutable, lo cual es un concepto extraño al principio, pero tiene sentido por algunas razones. Si desea modificar el estado, debe enviar un acción, que básicamente toma algunos argumentos, forma una carga útil y la envía a Redux. Redux pasa el estado actual a un reductor función, que modifica el estado existente y devuelve un nuevo estado que reemplaza al actual y activa una recarga de los componentes afectados. Por ejemplo, puede tener un reductor para agregar un nuevo elemento a una lista o eliminar o editar uno que ya existe.

Hacerlo de esta manera significa que nunca obtendrá ningún comportamiento indefinido con el estado de modificación de su aplicación a voluntad. Además, debido a que hay un registro de cada acción y lo que cambió, permite la depuración de viajes en el tiempo, donde puede desplazar el estado de su aplicación para depurar lo que sucede con cada acción (muy parecido a un historial de git).

Un registro de cada acción

Redux se puede usar con cualquier marco de interfaz, pero se usa comúnmente con React, y eso es en lo que nos enfocaremos aquí. Debajo del capó, Redux usa React’s API de contexto, que funciona de manera similar a Redux y es bueno para aplicaciones simples si desea renunciar a Redux por completo. Sin embargo, las Devtools de Redux son fantásticas cuando se trabaja con datos complejos y, en realidad, están más optimizadas para evitar reproducciones innecesarias.

Si está utilizando TypeScript, las cosas son mucho más complicadas para obtener Redux estrictamente mecanografiado. Querrás seguir esta guía en su lugar, que usa typesafe-actions para manejar las acciones y los reductores de una manera amigable con el tipo.

Estructurando su proyecto

Primero, querrá diseñar la estructura de su carpeta. Esto depende de ti y de las preferencias de estilo de tu equipo, pero básicamente hay dos patrones principales que utilizan la mayoría de los proyectos de Redux. El primero es simplemente dividir cada tipo de archivo (acción, reductor, middleware, efecto secundario) en su propia carpeta, así:

store/
  actions/
  reducers/
  sagas/
  middleware/
  index.js

Sin embargo, este no es el mejor, ya que a menudo necesitará un archivo de acción y un archivo reductor para cada función que agregue. Es mejor combinar las carpetas de acciones y reductores y dividirlas por función. De esta forma, cada acción y el reductor correspondiente están en el mismo archivo. usted

store/
  features/
    todo/
    etc/
  sagas/
  middleware/
  root-reducer.js
  root-action.js
  index.js

Esto limpia las importaciones, ya que ahora puede importar tanto las acciones como los reductores en la misma declaración usando:

import { todosActions, todosReducer } from 'store/features/todos'

Depende de usted si desea mantener el código de Redux en su propia carpeta (/store en los ejemplos anteriores), o intégrelo en la carpeta raíz src de su aplicación. Si ya está separando código por componente y está escribiendo muchas acciones personalizadas y reductores para cada componente, es posible que desee fusionar el /features/ y /components/ carpetas y almacenar componentes JSX junto con el código reductor.

Si está utilizando Redux con TypeScript, puede agregar un archivo adicional en cada carpeta de funciones para definir sus tipos.

Instalación y configuración de Redux

Instale Redux y React-Redux desde NPM:

npm install redux react-redux

Probablemente también querrás redux-devtools:

npm install --save-dev redux-devtools

Lo primero que querrás crear es tu tienda. Guardar esto como /store/index.js

import { createStore } from 'redux'
import rootReducer from './root-reducer'

const store = createStore(rootReducer)

export default store;

Por supuesto, su tienda se volverá más complicada que esto a medida que agregue cosas como complementos de efectos secundarios, middleware y otras utilidades como connected-react-router, pero esto es todo lo que se requiere por ahora. Este archivo toma el reductor raíz y llama createStore() usándolo, que se exporta para que lo use la aplicación.

A continuación, crearemos una función de lista de tareas sencilla. Probablemente desee comenzar por definir las acciones que requiere esta función y los argumentos que se les pasan. Crear un /features/todos/ carpeta y guarde lo siguiente como types.js:

export const ADD = 'ADD_TODO'
export const DELETE = 'DELETE_TODO'
export const EDIT = 'EDIT_TODO'

Esto define algunas constantes de cadena para los nombres de las acciones. Independientemente de los datos que esté pasando, cada acción tendrá un type propiedad, que es una cadena única que identifica la acción.

No es necesario que tenga un archivo de tipo como este, ya que puede escribir el nombre de cadena de la acción, pero es mejor para la interoperabilidad hacerlo de esta manera. Por ejemplo, podrías tener todos.ADD y reminders.ADD en la misma aplicación, lo que le ahorra la molestia de escribir _TODO o _REMINDER cada vez que hace referencia a una acción para esa función.

A continuación, guarde lo siguiente como /store/features/todos/actions.js:

import * as types from './types.js'

export const addTodo = text => ({ type: types.ADD, text })
export const deleteTodo = id => ({ type: types.DELETE, id })
export const editTodo = (id, text) => ({ type: types.EDIT, id, text })

Esto define algunas acciones utilizando los tipos de las constantes de cadena, presentando los argumentos y la creación de carga útil para cada una. Estos no tienen que ser completamente estáticos, ya que son funciones; un ejemplo que puede usar es configurar un tiempo de ejecución CUID para determinadas acciones.

El fragmento de código más complicado, y donde implementará la mayor parte de su lógica empresarial, es en los reductores. Estos pueden tomar muchas formas, pero la configuración más comúnmente utilizada es con una instrucción de cambio que maneja cada caso según el tipo de acción. Guardar esto como reducer.js:

import * as types from './types.js'

const initialState = [
  {
    text: 'Hello World',
    id: 0
  }
]

export default function todos(state = initialState, action) {
  switch (action.type) {
    case types.ADD:
      return [
        ...state,
        {
          id: state.reduce((maxId, todo) => Math.max(todo.id, maxId), -1) + 1,
          text: action.text
        }
      ]    

    case types.DELETE:
      return state.filter(todo =>
        todo.id !== action.id
      )

    case types.EDIT:
      return state.map(todo =>
        todo.id === action.id ? { ...todo, text: action.text } : todo
      )

    default:
      return state
  }
}

El estado se pasa como un argumento y cada caso devuelve una versión modificada del estado. En este ejemplo, ADD_TODO agrega un nuevo elemento al estado (con una nueva identificación cada vez), DELETE_TODO elimina todos los elementos con el ID proporcionado, y EDIT_TODO mapea y reemplaza el texto del elemento con el ID proporcionado.

El estado inicial también debe definirse y pasarse a la función reductora como valor predeterminado para la variable de estado. Por supuesto, esto no define toda la estructura de estado de Redux, solo el state.todos sección.

Estos tres archivos generalmente están separados en aplicaciones más complejas, pero si lo desea, también puede definirlos todos en un solo archivo, solo asegúrese de importar y exportar correctamente.

Con esa función completa, conectémosla a Redux (y a nuestra aplicación). En /store/root-reducer.js, importe todosReducer (y cualquier otro reductor de funciones del /features/ carpeta), luego páselo a combineReducers(), formando un reductor de raíz de nivel superior que se pasa a la tienda. Aquí es donde configurará el estado raíz, asegurándose de mantener cada característica en su propia rama.

import { combineReducers } from 'redux';

import todosReducer from './features/todos/reducer';

const rootReducer = combineReducers({
  todos: todosReducer
})

export default rootReducer

Usando Redux en React

Por supuesto, nada de esto es útil si no está conectado a React. Para hacerlo, tendrá que envolver toda su aplicación en un Proveedor componente. Esto asegura que el estado y los enlaces necesarios se transmitan a todos los componentes de su aplicación.

En App.js o index.js, donde sea que tenga su función de renderizado raíz, envuelva su aplicación en un <Provider>y páselo a la tienda (importado de /store/index.js) como utilería:

import React from 'react';
import ReactDOM from 'react-dom';

// Redux Setup
import { Provider } from 'react-redux';
import store, { history } from './store';

ReactDOM.render(
    <Provider store={store}>
       <App/>
    </Provider>
    , document.getElementById('root'));

Ahora puede utilizar Redux en sus componentes. El método más sencillo es con componentes de función y ganchos. Por ejemplo, para enviar una acción, utilizará el useDispatch() gancho, que le permite llamar a acciones directamente, p. ej. dispatch(todosActions.addTodo(text)).

El siguiente contenedor tiene una entrada conectada al estado React local, que se usa para agregar una nueva tarea al estado cada vez que se hace clic en un botón:

import React, { useState } from 'react';

import './Home.css';

import { TodoList } from 'components'
import { todosActions } from 'store/features/todos'
import { useDispatch } from 'react-redux'

function Home() {
  const dispatch = useDispatch();
  const [text, setText] = useState("");

  function handleClick() {
    dispatch(todosActions.addTodo(text));
    setText("");
  }

  function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
    setText(e.target.value);
  }

  return (
    <div className="App">
      <header className="App-header">

        <input type="text" value={text} onChange={handleChange} />

        <button onClick={handleClick}>
          Add New Todo
        </button>

        <TodoList />
      </header>
    </div>
  );
}

export default Home;

Luego, cuando desee hacer uso de los datos almacenados en el estado, use el useSelector gancho. Esto toma una función que selecciona parte del estado para su uso en la aplicación. En este caso, establece el post variable a la lista actual de todos. Esto luego se usa para representar un nuevo elemento de tarea para cada entrada en state.todos.

import React from 'react';
import { useSelector } from 'store'

import { Container, List, ListItem, Title } from './styles'

function TodoList() {
  const posts = useSelector(state => state.todos)

  return (
    <Container>
      <List>
        {posts.map(({ id, title }) => (
          <ListItem key={title}>
            <Title>{title} : {id}</Title>
          </ListItem>
        ))}
      </List>
    </Container>
  );
}

export default TodoList;

Realmente puedes crear funciones de selector personalizadas para manejar esto por ti, guardado en el /features/ carpeta al igual que acciones y reductores.

Una vez que tenga todo configurado y resuelto, es posible que desee considerar la configuración Herramientas de desarrollo de Redux, configurando middleware como Redux Logger o connected-react-router, o instalando un modelo de efectos secundarios como Redux Sagas.

Deja un comentario

En esta web usamos cookies para personalizar tu experiencia de usuario.    Política de cookies
Privacidad