Estructura del código
Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →
Preguntas frecuentes de Redux: Estructura del código
¿Cómo debería ser la estructura de mis archivos? ¿Cómo agrupo mis creadores de acciones y reductores? ¿Dónde coloco mis selectores?
Como Redux es simplemente una biblioteca de almacenamiento de datos, no tiene una opinión directa sobre cómo estructurar tu proyecto. Sin embargo, existen algunos patrones comunes que suelen utilizar la mayoría de desarrolladores de Redux:
-
Estilo Rails: carpetas separadas para "actions", "constants", "reducers", "containers" y "components"
-
"Carpetas por funcionalidad" / Estilo "Dominio": carpetas separadas por cada funcionalidad o dominio, posiblemente con subcarpetas por tipo de archivo
-
"Ducks/Slices": similar al estilo por dominio, pero vinculando explícitamente acciones y reductores, generalmente definiéndolos en el mismo archivo
Generalmente se recomienda definir los selectores junto a los reductores y exportarlos, para luego reutilizarlos en otros lugares (como en funciones mapStateToProps, creadores de acciones asíncronas o sagas). Esto permite centralizar todo el código que conoce la estructura real del árbol de estado en los archivos de reductores.
Recomendamos específicamente organizar tu lógica en "carpetas por funcionalidad", con toda la lógica de Redux para una funcionalidad concreta en un único archivo "slice/ducks".
Consulta este ejemplo:
Detailed Explanation: Example Folder Structure
An example folder structure might look something like:
/srcindex.tsx: Entry point file that renders the React component tree/appstore.ts: store setuprootReducer.ts: root reducer (optional)App.tsx: root React component
/common: hooks, generic components, utils, etc/features: contains all "feature folders"/todos: a single feature foldertodosSlice.ts: Redux reducer logic and associated actionsTodos.tsx: a React component
/app contains app-wide setup and layout that depends on all the other folders.
/common contains truly generic and reusable utilities and components.
/features has folders that contain all functionality related to a specific feature. In this example, todosSlice.ts is a "duck"-style file that contains a call to RTK's createSlice() function, and exports the slice reducer and action creators.
Aunque al final no importa cómo organices tu código en disco, es importante recordar que las acciones y reductores no deben considerarse de forma aislada. Es totalmente posible (y recomendable) que un reductor definido en una carpeta responda a una acción definida en otra carpeta.
Más información
Documentación
-
Guía de estilo: Organizar archivos en carpetas por funcionalidad con lógica en un único archivo
-
FAQ: Acciones - "¿Correspondencia 1:1 entre reductores y acciones?"
Artículos
-
Cómo escalar aplicaciones React (charla relacionada: Escalando aplicaciones React)
-
Una mejor estructura de archivos para aplicaciones React/Redux
-
Mi camino hacia una estructura de proyecto mantenible para React/Redux
-
Enlaces React/Redux: Arquitectura - Estructura de archivos del proyecto
Debates
-
#839: Enfatizar la definición de selectores junto a los reductores
-
Stack Overflow: Cómo estructurar componentes/contenedores en Redux
-
Twitter: No existe la estructura de archivos definitiva para Redux
¿Cómo debería repartir mi lógica entre reductores y creadores de acciones? ¿Dónde debe ir mi "lógica de negocio"?
No hay una respuesta única y clara sobre qué partes de la lógica deben ir en un reductor o en un creador de acciones. Algunos desarrolladores prefieren tener creadores de acciones "gruesos" con reductores "finos" que simplemente toman los datos de una acción y los fusionan ciegamente en el estado correspondiente. Otros intentan mantener las acciones lo más pequeñas posible y minimizar el uso de getState() en un creador de acciones. (Para este propósito, otros enfoques asíncronos como sagas y observables entran en la categoría de "creador de acciones").
Hay varios beneficios potenciales al poner más lógica en tus reductores. Es probable que los tipos de acción sean más semánticos y significativos (como "USER_UPDATED" en lugar de "SET_STATE"). Además, tener más lógica en los reductores significa que más funcionalidad se verá afectada por la depuración de viaje en el tiempo.
Este comentario resume bien la dicotomía:
El problema es decidir qué poner en el creador de acciones y qué en el reductor, la elección entre objetos de acción gruesos y finos. Si pones toda la lógica en el creador de acciones, terminas con objetos de acción gruesos que básicamente declaran las actualizaciones del estado. Los reductores se vuelven puros, simples, funciones de "añade esto, elimina aquello, actualiza esto otro". Serán fáciles de componer. Pero no mucha de tu lógica de negocio estará allí. Si pones más lógica en el reductor, terminas con objetos de acción agradables y finos, la mayor parte de tu lógica de datos en un solo lugar, pero tus reductores son más difíciles de componer ya que podrías necesitar información de otras ramas. Terminas con reductores grandes o reductores que toman argumentos adicionales de niveles superiores del estado.
Recomendamos poner tanta lógica como sea posible en los reductores. Hay ocasiones en las que necesitarás cierta lógica para preparar lo que va en la acción, pero los reductores deberían hacer la mayor parte del trabajo.
Más información
Documentación
-
Guía de estilo: Pon tanta lógica como sea posible en los reductores
-
Guía de estilo: Modela las acciones como "eventos", no como "setters"
Artículos
-
¿Dónde coloco mi lógica de negocio en una aplicación React/Redux?
-
El Tao de Redux, Parte 2 - Práctica y Filosofía. Reductores delgados y gruesos.
Debates
-
Cómo poner demasiada lógica en creadores de acciones afecta la depuración
-
#384: Cuanto más contiene un reductor, más puedes reproducir mediante viaje en el tiempo
-
#1171: Recomendaciones de mejores prácticas sobre creadores de acciones, reductores y selectores
-
Stack Overflow: ¿Acceder al estado de Redux en un creador de acciones?
¿Por qué debería usar creadores de acciones?
Redux no exige usar creadores de acciones. Eres libre de crear acciones como mejor te parezca, incluso simplemente pasando un objeto literal a dispatch. Los creadores de acciones surgieron de la arquitectura Flux y fueron adoptados por la comunidad Redux porque ofrecen varios beneficios.
Los creadores de acciones son más mantenibles. Las actualizaciones de una acción se pueden hacer en un solo lugar y aplicar en todas partes. Todas las instancias de una acción están garantizadas de tener la misma estructura y los mismos valores predeterminados.
Los creadores de acciones son testables. La corrección de una acción inline debe verificarse manualmente. Como cualquier función, los tests para un creador de acciones se escriben una vez y se ejecutan automáticamente.
Los creadores de acciones son más fáciles de documentar. Los parámetros del creador de acciones enumeran las dependencias de la acción. Y la centralización de la definición de la acción proporciona un lugar conveniente para comentarios de documentación. Cuando las acciones se escriben inline, esta información es más difícil de capturar y comunicar.
Los creadores de acciones son una abstracción más poderosa. Crear una acción a menudo implica transformar datos o realizar solicitudes AJAX. Los creadores de acciones proporcionan una interfaz uniforme para esta lógica variada. Esta abstracción libera a un componente de complicarse con los detalles de creación de una acción al despacharla.
Más información
Artículos
Debates
¿Dónde deberían vivir las conexiones websockets y otras persistentes?
El middleware es el lugar adecuado para conexiones persistentes como websockets en una aplicación Redux, por varias razones:
-
El middleware existe durante toda la vida de la aplicación
-
Al igual que con el almacén, probablemente solo necesitas una única instancia de conexión que toda la aplicación pueda usar
-
El middleware puede ver todas las acciones despachadas y despachar acciones él mismo. Esto significa que un middleware puede tomar acciones despachadas y convertirlas en mensajes enviados a través del websocket, y despachar nuevas acciones cuando se recibe un mensaje por el websocket.
-
Una instancia de conexión websocket no es serializable, por lo que no pertenece al estado del almacén en sí
Existen muchos middlewares disponibles para websockets y conexiones similares; consulta el enlace a continuación.
Bibliotecas
¿Cómo puedo usar el almacén de Redux en archivos que no son componentes?
Debe haber solo un almacén Redux por aplicación, lo que lo convierte efectivamente en un singleton en la arquitectura de la aplicación. Cuando se usa con React, el almacén se inyecta en los componentes en tiempo de ejecución al renderizar un <Provider store={store}> alrededor del componente raíz <App>, por lo que solo la lógica de configuración de la aplicación necesita importar el almacén directamente.
Sin embargo, puede haber ocasiones en las que otras partes del código necesiten interactuar con el almacén.
Debes evitar importar el almacén directamente en otros archivos del código. Aunque puede funcionar en algunos casos, esto a menudo genera errores de dependencias circulares en las importaciones.
Algunas soluciones posibles son:
-
Escribe tu lógica dependiente del almacén como un thunk, y luego despacha ese thunk desde un componente
-
Pasa referencias a
dispatchdesde los componentes como argumentos a las funciones relevantes -
Escribe la lógica como middleware y agrégalo al almacén durante la configuración
-
Inyecta la instancia del almacén en los archivos relevantes durante la creación de la aplicación
Un caso de uso común es leer información de autorización de API (como un token) del estado de Redux dentro de un interceptor de Axios. El archivo del interceptor necesita referenciar store.getState(), pero también debe importarse en archivos de la capa de API, lo que genera importaciones circulares.
Puedes exponer una función injectStore desde el archivo del interceptor:
let store
export const injectStore = _store => {
store = _store
}
axiosInstance.interceptors.request.use(config => {
config.headers.authorization = store.getState().auth.token
return config
})
Luego, en tu archivo de punto de entrada, inyecta la store en el archivo de configuración de la API:
import store from './app/store'
import { injectStore } from './common/api'
injectStore(store)
De esta manera, la configuración de la aplicación es el único código que debe importar el almacén, y el gráfico de dependencias de archivos evita dependencias circulares.