Ir al contenido principal

Redux Essentials, Parte 3: Flujo de datos básico en Redux

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Qué aprenderás
  • Cómo configurar un store de Redux en una aplicación React
  • Cómo añadir "slices" de lógica reductora al store con createSlice
  • Leer datos de Redux en componentes con el hook useSelector
  • Despachar acciones en componentes con el hook useDispatch
Requisitos previos

Introducción

En la Parte 1: Resumen y conceptos de Redux, vimos cómo Redux puede ayudarnos a construir aplicaciones mantenibles al proporcionarnos un lugar central único para el estado global de la aplicación. También discutimos conceptos fundamentales de Redux como despachar objetos de acción, usar funciones reductoras que devuelven nuevos valores de estado, y escribir lógica asíncrona usando thunks. En la Parte 2: Estructura de aplicación con Redux Toolkit, vimos cómo APIs como configureStore y createSlice de Redux Toolkit junto con Provider y useSelector de React-Redux trabajan juntos para permitirnos escribir lógica de Redux e interactuar con ella desde nuestros componentes React.

Ahora que tienes una idea de qué son estas piezas, es hora de poner ese conocimiento en práctica. Vamos a construir una pequeña aplicación de feed para redes sociales, que incluirá varias características que demuestran casos de uso del mundo real. Esto te ayudará a entender cómo usar Redux en tus propias aplicaciones.

Usaremos sintaxis de TypeScript para escribir nuestro código. Puedes usar Redux con JavaScript plano, pero TypeScript ayuda a prevenir muchos errores comunes, proporciona documentación incorporada para tu código, y permite que tu editor muestre qué tipos de variables se necesitan en lugares como componentes React y reductores de Redux. Recomendamos encarecidamente usar TypeScript para todas las aplicaciones Redux.

precaución

La aplicación de ejemplo no está pensada como un proyecto completo listo para producción. El objetivo es ayudarte a aprender las APIs de Redux y patrones de uso típicos, y orientarte en la dirección correcta usando ejemplos limitados. Además, algunas de las primeras piezas que construiremos se actualizarán más adelante para mostrar mejores formas de hacer las cosas. Por favor, lee todo el tutorial para ver todos los conceptos en uso.

Configuración del proyecto

Para este tutorial, hemos creado un proyecto inicial preconfigurado que ya tiene React y Redux configurados, incluye estilos predeterminados y cuenta con una API REST falsa que nos permitirá escribir solicitudes de API reales en nuestra aplicación. Usarás esto como base para escribir el código real de la aplicación.

Para comenzar, puedes abrir y hacer un fork de este CodeSandbox:

También puedes clonar el mismo proyecto desde este repositorio de GitHub. El proyecto está configurado para usar Yarn 4 como gestor de paquetes, pero puedes usar cualquier gestor (NPM, PNPM o Bun) según prefieras. Después de instalar los paquetes, puedes iniciar el servidor de desarrollo local con el comando yarn dev.

Si deseas ver la versión final de lo que vamos a construir, puedes consultar la rama tutorial-steps-ts o ver la versión final en este CodeSandbox.

Agradecemos a Tania Rascia, cuyo tutorial Using Redux with React inspiró este ejemplo. También utiliza su plantilla CSS Primitive UI para los estilos.

Creación de un nuevo proyecto Redux + React

Una vez finalizado este tutorial, probablemente querrás trabajar en tus propios proyectos. Recomendamos usar las plantillas de Redux para Vite y Next.js como la forma más rápida de crear un nuevo proyecto Redux + React. Las plantillas incluyen Redux Toolkit y React-Redux ya configurados, usando el mismo ejemplo de aplicación "contador" que viste en la Parte 1. Esto te permite comenzar directamente a escribir el código de tu aplicación sin tener que añadir los paquetes de Redux ni configurar el store.

Explorando el proyecto inicial

Echemos un vistazo rápido al contenido inicial del proyecto:

  • /public: estilos CSS base y otros archivos estáticos como iconos

  • /src

    • main.tsx: archivo de entrada de la aplicación, que renderiza el componente <App>. En este ejemplo, también configura la API REST falsa al cargar la página.
    • App.tsx: componente principal de la aplicación. Renderiza la barra de navegación superior y gestiona el enrutamiento cliente para el resto del contenido.
    • index.css: estilos para toda la aplicación
    • /api
      • client.ts: cliente pequeño basado en fetch que permite hacer peticiones HTTP GET y POST
      • server.ts: proporciona una API REST falsa para nuestros datos. Más tarde nuestra aplicación obtendrá datos de estos endpoints ficticios.
    • /app
      • Navbar.tsx: renderiza la cabecera superior y el contenido de navegación

Si cargas la aplicación ahora, deberías ver la cabecera y un mensaje de bienvenida, pero sin funcionalidad.

¡Con esto, empecemos!

Configuración del store de Redux

Actualmente el proyecto está vacío, así que necesitaremos comenzar con la configuración única de las partes de Redux.

Añadiendo los paquetes de Redux

Si miras package.json, verás que ya hemos instalado los dos paquetes necesarios para usar Redux:

  • @reduxjs/toolkit: el paquete moderno de Redux, que incluye todas las funciones que usaremos para construir la aplicación

  • react-redux: las funciones necesarias para que tus componentes React se comuniquen con un store de Redux

Si estás configurando un proyecto desde cero, comienza añadiendo tú mismo esos paquetes al proyecto.

Creación del store

El primer paso es crear un store de Redux real. Uno de los principios de Redux es que debe haber solo una instancia de store para toda la aplicación.

Normalmente creamos y exportamos la instancia del store de Redux en su propio archivo. La estructura de carpetas real de la aplicación depende de ti, pero es estándar tener la configuración general en una carpeta src/app/.

Comenzaremos añadiendo un archivo src/app/store.ts y creando el store.

Redux Toolkit incluye un método llamado configureStore. Esta función crea una nueva instancia del store de Redux. Tiene varias opciones que puedes pasar para modificar su comportamiento. También aplica automáticamente las configuraciones más comunes y útiles, incluyendo verificación de errores típicos y habilitar la extensión Redux DevTools para que puedas ver el estado y el historial de acciones.

src/app/store.ts
import { configureStore } from '@reduxjs/toolkit'
import type { Action } from '@reduxjs/toolkit'

interface CounterState {
value: number
}

// An example slice reducer function that shows how a Redux reducer works inside.
// We'll replace this soon with real app logic.
function counterReducer(state: CounterState = { value: 0 }, action: Action) {
switch (action.type) {
// Handle actions here
default: {
return state
}
}
}

export const store = configureStore({
// Pass in the root reducer setup as the `reducer` argument
reducer: {
// Declare that `state.counter` will be updated by the `counterReducer` function
counter: counterReducer
}
})

configureStore siempre requiere una opción reducer. Normalmente debe ser un objeto que contenga los "reductores por segmento" individuales para las diferentes partes de la aplicación. (Si es necesario, también puedes crear la función reductora raíz por separado y pasarla como argumento reducer).

En este primer paso, estamos pasando una función reductora de segmento ficticia para counter, para mostrar cómo es la configuración. Reemplazaremos esto con un reductor real para la aplicación que construiremos en un momento.

Configuración con Next.js

Si estás usando Next.js, el proceso de configuración requiere algunos pasos adicionales. Consulta la página Configuración con Next.js para detalles sobre cómo configurar Redux con Next.js.

Proporcionando el Store

Redux por sí mismo es una biblioteca JS estándar y puede funcionar con cualquier capa de UI. En esta aplicación, estamos usando React, por lo que necesitamos una forma para que nuestros componentes React interactúen con el store de Redux.

Para que esto funcione, necesitamos usar la biblioteca React-Redux y pasar el store de Redux a un componente <Provider>. Esto utiliza Context API de React para hacer que el store de Redux sea accesible para todos los componentes React en nuestra aplicación.

consejo

¡Es importante que no debemos intentar importar directamente el store de Redux en otros archivos de código de la aplicación! Como solo hay un archivo de store, importarlo directamente puede causar accidentalmente problemas de importaciones circulares (donde el archivo A importa a B, que importa a C, que importa a A), lo que lleva a errores difíciles de rastrear. Además, queremos poder escribir tests para componentes y lógica de Redux, y esos tests necesitarán crear sus propias instancias de store de Redux. Proporcionar el store a los componentes mediante Context mantiene esta flexibilidad y evita problemas de importación.

Para hacer esto, importaremos el store en el archivo de entrada main.tsx, envolveremos el componente <App> con un <Provider> que recibe el store:

src/main.tsx
import { createRoot } from 'react-dom/client'
import { Provider } from 'react-redux'

import App from './App'
import { store } from './app/store'

// skip mock API setup

const root = createRoot(document.getElementById('root')!)

root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
)

Inspeccionando el Estado de Redux

Ahora que tenemos un store, podemos usar la extensión Redux DevTools para ver el estado actual de Redux.

Si abres las herramientas de desarrollo de tu navegador (por ejemplo haciendo clic derecho en cualquier parte de la página y eligiendo "Inspeccionar"), puedes hacer clic en la pestaña "Redux". Esto mostrará el historial de acciones despachadas y el valor actual del estado:

Redux DevTools: estado inicial de la aplicación

El estado actual debería ser un objeto que se parece a esto:

{
counter: {
value: 0
}
}

Esa estructura fue definida por la opción reducer que pasamos a configureStore: un objeto con un campo llamado counter, y el reductor del segmento para el campo counter devuelve un objeto como {value} como su estado.

Exportando Tipos del Store

Como estamos usando TypeScript, frecuentemente necesitaremos referirnos a tipos TS para "el tipo del estado de Redux" y "el tipo de la función dispatch del store de Redux".

Necesitamos exportar esos tipos desde el archivo store.ts. Definiremos los tipos usando el operador typeof de TS para que TypeScript infiera los tipos basados en la definición del store de Redux:

src/app/store.ts
import { configureStore } from '@reduxjs/toolkit'

// omit counter slice setup

export const store = configureStore({
reducer: {
counter: counterReducer
}
})

// Infer the type of `store`
export type AppStore = typeof store
// Infer the `AppDispatch` type from the store itself
export type AppDispatch = typeof store.dispatch
// Same for the `RootState` type
export type RootState = ReturnType<typeof store.getState>

Si pasas el cursor sobre el tipo RootState en tu editor, deberías ver type RootState = { counter: CounterState; }. Como este tipo se deriva automáticamente de la definición del store, todos los cambios futuros en la configuración del reducer se reflejarán automáticamente en el tipo RootState. Así solo necesitamos definirlo una vez, y siempre será preciso.

Exportando Hooks Tipados

Vamos a usar extensivamente los hooks useSelector y useDispatch de React-Redux en nuestros componentes. Esos hooks necesitan referenciar los tipos RootState y AppDispatch cada vez que los usemos.

Podemos simplificar el uso y evitar repetir los tipos si configuramos versiones "pre-tipadas" de esos hooks que ya incorporan los tipos correctos.

React-Redux 9.1 incluye métodos .withTypes() que aplican los tipos correctos a esos hooks. Podemos exportar estos hooks pre-tipados y luego usarlos en el resto de la aplicación:

src/app/hooks.ts
// This file serves as a central hub for re-exporting pre-typed Redux hooks.
import { useDispatch, useSelector } from 'react-redux'
import type { AppDispatch, RootState } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()

¡Con esto completamos el proceso de configuración! Empecemos a construir la aplicación.

Feed principal de publicaciones

La función principal de nuestra aplicación de feed de redes sociales será una lista de publicaciones. Añadiremos más elementos a esta función a medida que avancemos, pero para empezar, nuestro primer objetivo es simplemente mostrar la lista de entradas de publicaciones en pantalla.

Creación del slice de publicaciones

El primer paso es crear un nuevo "slice" de Redux que contendrá los datos de nuestras publicaciones.

Un "slice" es una colección de lógica de reducer y acciones de Redux para una única función en tu aplicación, típicamente definidos juntos en un solo archivo. El nombre proviene de dividir el objeto de estado raíz de Redux en múltiples "slices" (porciones) de estado.

Una vez que tengamos los datos de las publicaciones en el store de Redux, podremos crear los componentes de React para mostrar esos datos en la página.

Dentro de src, crea una nueva carpeta features, coloca una carpeta posts dentro de features, y agrega un nuevo archivo llamado postsSlice.ts.

Vamos a usar la función createSlice de Redux Toolkit para crear una función reducer que sepa manejar nuestros datos de publicaciones. Las funciones reducer necesitan tener algunos datos iniciales para que el store de Redux tenga esos valores cargados al iniciar la aplicación.

Por ahora, crearemos un array con algunos objetos de publicación ficticios para poder empezar a implementar la interfaz de usuario.

Importaremos createSlice, definiremos nuestro array inicial de publicaciones, lo pasaremos a createSlice y exportaremos la función reducer de publicaciones que createSlice genere para nosotros:

features/posts/postsSlice.ts
import { createSlice } from '@reduxjs/toolkit'

// Define a TS type for the data we'll be using
export interface Post {
id: string
title: string
content: string
}

// Create an initial state value for the reducer, with that type
const initialState: Post[] = [
{ id: '1', title: 'First Post!', content: 'Hello!' },
{ id: '2', title: 'Second Post', content: 'More text' }
]

// Create the slice and pass in the initial state
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {}
})

// Export the generated reducer function
export default postsSlice.reducer

Cada vez que creamos un nuevo slice, necesitamos añadir su función reducer a nuestro store de Redux. Ya tenemos un store de Redux creado, pero actualmente no contiene datos. Abre app/store.ts, importa la función postsReducer, elimina todo el código relacionado con counter y actualiza la llamada a configureStore para que postsReducer se pase como un campo reducer llamado posts:

app/store.ts
import { configureStore } from '@reduxjs/toolkit'

// Removed the `counterReducer` function, `CounterState` type, and `Action` import

import postsReducer from '@/features/posts/postsSlice'

export const store = configureStore({
reducer: {
posts: postsReducer
}
})

Esto le indica a Redux que queremos que nuestro objeto de estado de nivel superior tenga un campo llamado posts, y que todos los datos para state.posts serán actualizados por la función postsReducer cuando se despachen acciones.

Podemos confirmar que esto funciona abriendo la extensión Redux DevTools y observando el contenido actual del estado:

Estado inicial de publicaciones

Mostrar la lista de publicaciones

Ahora que tenemos datos de publicaciones en nuestro store, podemos crear un componente de React que muestre la lista de publicaciones. Todo el código relacionado con la función de publicaciones del feed debe ir en la carpeta posts, así que crea un nuevo archivo llamado PostsList.tsx allí. (Nota: como es un componente de React escrito en TypeScript usando sintaxis JSX, necesita extensión .tsx para que TypeScript lo compile correctamente)

Si vamos a renderizar una lista de publicaciones, necesitamos obtener los datos de algún lugar. Los componentes de React pueden leer datos del store de Redux usando el hook useSelector de la biblioteca React-Redux. Las "funciones selectoras" que escribas recibirán el objeto completo de estado state de Redux como parámetro y deben devolver los datos específicos que este componente necesita del store.

Como estamos usando TypeScript, todos nuestros componentes deben usar siempre el hook pre-tipado useAppSelector que añadimos en src/app/hooks.ts, ya que este ya incorpora el tipo RootState correcto.

Nuestro componente inicial PostsList leerá el valor state.posts de la tienda Redux, luego recorrerá el array de publicaciones y mostrará cada una en pantalla:

features/posts/PostsList.tsx
import { useAppSelector } from '@/app/hooks'

export const PostsList = () => {
// Select the `state.posts` value from the store into the component
const posts = useAppSelector(state => state.posts)

const renderedPosts = posts.map(post => (
<article className="post-excerpt" key={post.id}>
<h3>{post.title}</h3>
<p className="post-content">{post.content.substring(0, 100)}</p>
</article>
))

return (
<section className="posts-list">
<h2>Posts</h2>
{renderedPosts}
</section>
)
}

Luego necesitamos actualizar el enrutamiento en App.tsx para mostrar el componente PostsList en lugar del mensaje de "bienvenida". Importa el componente PostsList en App.tsx y reemplaza el texto de bienvenida con <PostsList />. También lo envolveremos en un Fragmento de React porque pronto añadiremos algo más a la página principal:

App.tsx
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'

import { Navbar } from './components/Navbar'
import { PostsList } from './features/posts/PostsList'

function App() {
return (
<Router>
<Navbar />
<div className="App">
<Routes>
<Route
path="/"
element={
<>
<PostsList />
</>
}
></Route>
</Routes>
</div>
</Router>
)
}

export default App

Una vez añadido, la página principal de nuestra aplicación debería verse así:

Lista inicial de publicaciones

¡Progreso! Hemos añadido datos a la tienda Redux y los mostramos en pantalla mediante un componente React.

Añadir nuevas publicaciones

Es agradable ver publicaciones escritas por otros, pero nos gustaría poder escribir las nuestras. Creemos un formulario "Añadir nueva publicación" que nos permita escribir y guardar posts.

Primero crearemos el formulario vacío y lo añadiremos a la página. Luego conectaremos el formulario a nuestra tienda Redux para que las nuevas publicaciones se añadan al hacer clic en "Guardar publicación".

Añadir el formulario de nueva publicación

Crea AddPostForm.tsx en nuestra carpeta posts. Añadiremos un campo de texto para el título y un área de texto para el cuerpo:

features/posts/AddPostForm.tsx
import React from 'react'

// TS types for the input fields
// See: https://epicreact.dev/how-to-type-a-react-form-on-submit-handler/
interface AddPostFormFields extends HTMLFormControlsCollection {
postTitle: HTMLInputElement
postContent: HTMLTextAreaElement
}
interface AddPostFormElements extends HTMLFormElement {
readonly elements: AddPostFormFields
}

export const AddPostForm = () => {
const handleSubmit = (e: React.FormEvent<AddPostFormElements>) => {
// Prevent server submission
e.preventDefault()

const { elements } = e.currentTarget
const title = elements.postTitle.value
const content = elements.postContent.value

console.log('Values: ', { title, content })

e.currentTarget.reset()
}

return (
<section>
<h2>Add a New Post</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="postTitle">Post Title:</label>
<input
type="text"
id="postTitle"
name="postTitle"
defaultValue=""
required
/>
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
defaultValue=""
required
/>
<button>Save Post</button>
</form>
</section>
)
}

Nota que esto aún no tiene lógica específica de Redux - la añadiremos a continuación.

En este ejemplo usamos inputs "no controlados" y validación HTML5 para evitar enviar campos vacíos, pero cómo obtienes los valores de un formulario es una preferencia de patrones en React, no específico de Redux.

Importa este componente en App.tsx y añádelo justo encima del componente <PostsList />:

App.tsx
// omit outer `<App>` definition
<Route
path="/"
element={
<>
<AddPostForm />
<PostsList />
</>
}
></Route>

Deberías ver el formulario aparecer en la página debajo del encabezado.

Guardar nuevas publicaciones

Ahora actualicemos nuestro slice de publicaciones para añadir nuevas entradas a la tienda Redux.

Nuestro slice de publicaciones es responsable de manejar todas las actualizaciones de datos. Dentro de createSlice hay un objeto llamado reducers. Actualmente está vacío. Necesitamos añadir una función reductora para manejar el caso de añadir una publicación.

Dentro de reducers, añade una función llamada postAdded, que recibirá dos argumentos: el valor actual state y el objeto action despachado. Como el slice de publicaciones solo conoce sus propios datos, el argumento state será el array de publicaciones en sí, no todo el estado Redux.

El objeto action tendrá nuestra nueva publicación como campo action.payload. Al declarar la función reductora, debemos indicar a TypeScript el tipo real de action.payload para verificar correctamente cuando pasemos el argumento y accedamos al contenido de action.payload. Para ello importamos el tipo PayloadAction de Redux Toolkit y declaramos el argumento action como action: PayloadAction<ThePayloadTypeHere>. En este caso será action: PayloadAction<Post>.

La actualización real del estado consiste en añadir el nuevo objeto de publicación al array state, lo que podemos hacer con state.push() en el reducer.

advertencia

Recuerda: ¡Las funciones reductoras de Redux deben siempre crear nuevos valores de estado de forma inmutable, haciendo copias! Es seguro llamar a funciones mutantes como Array.push() o modificar campos de objetos como state.someField = someValue dentro de createSlice(), porque convierte internamente esas mutaciones en actualizaciones inmutables seguras usando la biblioteca Immer, pero ¡no intentes mutar datos fuera de createSlice!

Cuando escribimos la función reductora postAdded, createSlice generará automáticamente una función "creadora de acciones" con el mismo nombre. Podemos exportar ese creador de acciones y usarlo en nuestros componentes de UI para despachar la acción cuando el usuario haga clic en "Guardar publicación".

features/posts/postsSlice.ts
// Import the `PayloadAction` TS type
import { createSlice, PayloadAction } from '@reduxjs/toolkit'

// omit initial state

const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
// Declare a "case reducer" named `postAdded`.
// The type of `action.payload` will be a `Post` object.
postAdded(state, action: PayloadAction<Post>) {
// "Mutate" the existing state array, which is
// safe to do here because `createSlice` uses Immer inside.
state.push(action.payload)
}
}
})

// Export the auto-generated action creator with the same name
export const { postAdded } = postsSlice.actions

export default postsSlice.reducer

En términos de nomenclatura, postAdded aquí es un ejemplo de un "reductor de caso". Es una función reductora, dentro de un slice, que maneja un tipo de acción específico que fue despachado. Conceptualemente, es como si escribiéramos una declaración case dentro de un switch: "cuando veamos este tipo de acción exacto, ejecutamos esta lógica":

function sliceReducer(state = initialState, action) {
switch (action.type) {
case 'posts/postAdded': {
// update logic here
}
}
}

Despachando la acción "Post Added"

Nuestro AddPostForm tiene campos de texto y un botón "Guardar publicación" que activa un manejador de envío, pero el botón aún no hace nada. Necesitamos actualizar el manejador de envío para despachar el creador de acciones postAdded y pasarle un nuevo objeto de publicación que contenga el título y contenido que escribió el usuario.

Nuestros objetos de publicación también necesitan tener un campo id. Actualmente, nuestras publicaciones de prueba iniciales usan números falsos para sus IDs. Podríamos escribir código que calcule cuál debería ser el próximo ID incremental, pero sería mejor generar un ID único aleatorio. Redux Toolkit tiene una función nanoid que podemos usar para eso.

información

Hablaremos más sobre generación de IDs y despacho de acciones en Parte 4: Usando datos de Redux.

Para despachar acciones desde un componente, necesitamos acceso a la función dispatch del store. Obtenemos esto llamando al hook useDispatch de React-Redux. Como estamos usando TypeScript, eso significa que debemos importar el hook useAppDispatch con los tipos correctos. También necesitamos importar el creador de acciones postAdded en este archivo.

Una vez que tenemos la función dispatch disponible en nuestro componente, podemos llamar a dispatch(postAdded()) en un manejador de clics. Podemos tomar los valores del título y contenido de nuestro formulario, generar un nuevo ID, y combinarlos en un nuevo objeto de publicación que pasamos a postAdded().

features/posts/AddPostForm.tsx
import React from 'react'
import { nanoid } from '@reduxjs/toolkit'

import { useAppDispatch } from '@/app/hooks'

import { type Post, postAdded } from './postsSlice'

// omit form types

export const AddPostForm = () => {
// Get the `dispatch` method from the store
const dispatch = useAppDispatch()


const handleSubmit = (e: React.FormEvent<AddPostFormElements>) => {
// Prevent server submission
e.preventDefault()

const { elements } = e.currentTarget
const title = elements.postTitle.value
const content = elements.postContent.value

// Create the post object and dispatch the `postAdded` action
const newPost: Post = {
id: nanoid(),
title,
content
}
dispatch(postAdded(newPost))

e.currentTarget.reset()
}

return (
<section>
<h2>Add a New Post</h2>
<form onSubmit={handleSubmit}>
<label htmlFor="postTitle">Post Title:</label>
<input
type="text"
id="postTitle"
name="postTitle"
defaultValue=""
required
/>
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
defaultValue=""
required
/>
<button>Save Post</button>
</form>
</section>
)
}

Ahora, intenta escribir un título y algo de texto, y haz clic en "Guardar publicación". Deberías ver un nuevo elemento para esa publicación aparecer en la lista de publicaciones.

¡Felicidades! ¡Acabas de construir tu primera aplicación React + Redux funcional!

Esto muestra el ciclo completo del flujo de datos en Redux:

  • Nuestra lista de publicaciones leyó el conjunto inicial de publicaciones del store con useSelector y renderizó la UI inicial

  • Despachamos la acción postAdded que contiene los datos para la nueva entrada de publicación

  • El reductor de publicaciones vio la acción postAdded y actualizó el array de publicaciones con la nueva entrada

  • El store de Redux notificó a la UI que algunos datos habían cambiado

  • La lista de publicaciones leyó el array actualizado y se volvió a renderizar para mostrar la nueva publicación

Todas las nuevas características que agreguemos después seguirán los mismos patrones básicos que has visto aquí: agregar slices de estado, escribir funciones reductoras, despachar acciones, y renderizar la UI basada en datos del store de Redux.

Podemos comprobar la extensión Redux DevTools para ver la acción que hemos despachado y observar cómo se actualizó el estado de Redux en respuesta a esa acción. Si hacemos clic en la entrada "posts/postAdded" en la lista de acciones, la pestaña "Action" debería verse así:

Contenidos de la acción postAdded

La pestaña "Diff" también debería mostrarnos que state.posts ha añadido un nuevo elemento en el índice 2.

Recuerda: ¡El almacén de Redux solo debe contener datos considerados "globales" para la aplicación! En este caso, solo el componente AddPostForm necesita conocer los últimos valores de los campos de entrada. Incluso si construyéramos el formulario con entradas "controladas", convendría mantener los datos en el estado del componente React en lugar de intentar guardar datos temporales en el almacén de Redux. Cuando el usuario termina con el formulario, despachamos una acción de Redux para actualizar el almacén con los valores finales basados en la entrada del usuario.

Lo que has aprendido

Hemos configurado los fundamentos de una aplicación Redux: almacén, slice con reductores e interfaz para despachar acciones. Así luce la aplicación hasta ahora:

Recapitulemos lo que has aprendido en esta sección:

Resumen
  • Una aplicación Redux tiene un único store que se pasa a los componentes React mediante un componente <Provider>
  • El estado de Redux se actualiza mediante "funciones reductoras":
    • Los reductores siempre calculan un nuevo estado inmutablemente, copiando valores existentes y modificando las copias con nuevos datos
    • La función createSlice de Redux Toolkit genera funciones reductoras de "slice" y permite escribir código "mutante" que se convierte en actualizaciones inmutables seguras
    • Estas funciones reductoras de slice se añaden al campo reducer en configureStore, definiendo los datos y nombres de campo en el almacén de Redux
  • Los componentes React leen datos del almacén con el hook useSelector:
    • Las funciones selectoras reciben todo el objeto state y deben devolver un valor
    • Los selectores se re-ejecutan cuando se actualiza el almacén de Redux, y si los datos devueltos cambian, el componente se vuelve a renderizar
  • Los componentes React despachan acciones para actualizar el almacén usando el hook useDispatch:
    • createSlice generará funciones creadoras de acciones para cada reductor que añadamos a un slice
    • Llama a dispatch(someActionCreator()) en un componente para despachar una acción
    • Los reductores se ejecutarán, comprobarán si la acción es relevante y devolverán nuevo estado si corresponde
    • Los datos temporales como valores de formulario deben mantenerse en el estado del componente React o en campos de entrada HTML simples. Despacha una acción de Redux para actualizar el almacén cuando el usuario termine con el formulario.
  • Si usas TypeScript, la configuración inicial debe definir tipos TS para RootState y AppDispatch basados en el almacén, y exportar versiones pre-tipadas de los hooks useSelector y useDispatch de React-Redux

¿Qué sigue?

Ahora que conoces el flujo básico de datos en Redux, pasa a la Parte 4: Usando datos de Redux, donde añadiremos funcionalidad adicional a nuestra aplicación y veremos ejemplos de cómo trabajar con los datos ya existentes en el almacén.