Los slices de características agrupan todo lo que un área de producto necesita en un directorio; las carpetas por layers (components/, hooks/, screens/ en la raíz del repo) dispersan código relacionado en todo el árbol. La división que elijas el primer día determina cuán dolorosa será la incorporación de las primeras 50 características.
Slices de características (verticales) se organizan por capacidad de producto - features/orders, features/inbox. Los cambios en Orders tocan archivos bajo un solo subárbol.
Layers (horizontales) se organizan por rol técnico - components/, hooks/, screens/ globales. Cada característica dispersa archivos en múltiples carpetas de nivel superior.
Expo Router app/ mapea URLs a screens. Los archivos de ruta deben permanecer como puntos de entrada delgados; la lógica pesada vive en features/.
APIs públicas index.ts hacen cumplir los límites de importación - los imports entre características usan @/features/orders, nunca @/features/orders/components/OrderRow.
components/ui/ compartido contiene primitivos del sistema de diseño usado por muchas características; components/ de características contiene composición específica del producto.
Incorporación, refactorización y PRs permanecen locales
Riesgo de duplicar filas de lista similares en características
Layered
screens/, hooks/, components/ en raíz
Familiar para desarrolladores web; fácil encontrar "todos los hooks"
Los imports entre carpetas explotan; emergen god screens
Híbrido (recomendado)
components/ui/ + features/* + app/ delgado
Primitivos compartidos sin un basurero de componentes globales
Requiere disciplina en exportaciones index.ts
# Layered - bien para < 5 screens, doloroso a escalasrc/├── components/ # filas, tarjetas, modales de cada característica mezclados├── hooks/├── screens/└── services/# Slice de características - escala con el equipo y cantidad de característicassrc/├── components/ui/├── lib/└── features/ ├── orders/ ├── inbox/ └── settings/
Los archivos de ruta importan solo desde @/features/<name>
Mantiene la navegación desacoplada de internals de características
Las características importan otras características a través de su index.ts
Previene acoplamiento de ruta profunda
Nunca importes directamente components/ de características hermanas
OrderRow no es un primitivo compartido - duplica o promociona a ui/
lib/ y components/ui/ son la capa compartida
Las utilidades transversales y tokens de diseño viven aquí
// ✅ Import entre características a través de API públicaimport type { Order } from "@/features/orders";// ❌ Accediendo a internals de otra característicaimport { OrderRow } from "@/features/orders/components/OrderRow";
// features/settings/screens/SettingsScreen.tsx - screen de características usa primitivo compartidoimport { Screen } from "@/components/ui/Screen";import { Text } from "react-native";export function SettingsScreen() { return ( <Screen title="Configuración"> <Text>Notificaciones, cuenta y privacidad viven aquí.</Text> </Screen> );}
Mantén components/ui/ libre de imports de características. Si Screen necesita datos de características, pásalos como props - no importes desde features/.
components/ global se convierte en un depósito - Cada tarjeta única aterriza en components/ porque "probablemente será reutilizable algún día". Solución: Por defecto a features/<name>/components/; promociona a ui/ solo después de que exista un segundo consumidor real.
Archivos de ruta gordos en app/ - Lógica de negocio, llamadas de búsqueda y modales viven en app/(tabs)/orders/index.tsx. Solución: Re-exporta desde features/orders; mantén las rutas bajo ~5 líneas.
Imports profundos en características - features/inbox importa features/orders/components/OrderRow. Solución: Importa tipos desde @/features/orders; duplica filas presentacionales pequeñas o extrae UI compartida a components/ui/.
Sin API pública index.ts - Cincuenta archivos por característica sin límite de exportación; todo es público. Solución: Exporta solo screens y tipos compartidos desde index.ts; trata otras rutas como privadas.
Carpetas de layer más carpetas de características - screens/OrdersScreen.tsx y features/orders/screens/OrdersScreen.tsx ambos existen. Solución: Elige un esquema por app; la confusión híbrida duplica el costo de búsqueda.
Hooks compartidos en la capa equivocada - useOrders en hooks/ global pero solo Orders lo usa. Solución: Coloca hooks de una sola característica bajo features/orders/hooks/; reserva global hooks/ para comportamiento verdaderamente compartido (useAppTheme).
Paquetes monorepo sin límites de características - Shared @repo/ui crece con props específicas de características. Solución: Mantén características del producto en la app; paquetes contienen solo primitivos. Ver Paquetes Compartidos y Resolución de Metro.
Un directorio que contiene todo lo que un área de producto necesita - screens, hooks, componentes, tipos - de modo que los ingenieros trabajan en un subárbol. Ejemplo: src/features/orders/.
¿Qué es una estructura layered?
Carpetas de nivel superior agrupadas por rol técnico: components/, hooks/, screens/, services/. Cada característica dispersa archivos en estas carpetas en lugar de un slice.
¿Qué va en app/ vs features/?
app/ contiene archivos de ruta de Expo Router - mapeo de URL y anidación de layout. features/ contiene implementación - screens, hooks y componentes de características. Los archivos de ruta deben re-exportar screens de características.
¿Qué va en components/ui/ vs features/*/components/?
components/ui/ - primitivos del sistema de diseño usado por muchas características (Button, Screen, TextField). features/*/components/ - UI específica del producto usado solo dentro de esa característica (OrderRow, InboxThread).
¿Por qué usar index.ts en cada característica?
Define la API pública. Otras características y rutas importan @/features/orders - no rutas profundas. Los internals pueden cambiar sin romper consumidores.
¿Pueden las características importar desde otras características?
Sí, pero solo a través del index.ts de la otra característica - típicamente tipos o un screen único exportado. Evita importar componentes internos; eso acopla características silenciosamente.
¿Cuándo debería usar carpetas layered en su lugar?
Apps muy pequeñas con pocos screens y un desarrollador. Una vez que los PRs rutinariamente tocan múltiples carpetas de nivel superior para una característica, migra a slices.
¿Cómo migro de layers a slices?
Elige el peor god screen. Crea features/<name>/, mueve su screen, hooks y componentes, añade index.ts y adelgaza el archivo de ruta. Repite por característica - no se requiere reescritura de gran escala.
¿Dónde viven los clientes de API y auth?
Infraestructura transversal en src/lib/ - api.ts, storage.ts, analytics.ts. Los hooks de características llaman a lib/; lib/ no importa desde features/.
¿Deberían los hooks ser globales o por-característica?
Por-característica cuando solo un screen los usa (useOrders en features/orders/hooks/). Global en src/hooks/ cuando se comparten en características (useAppTheme, useSession).
Error común: ¿por qué nuestra carpeta de componentes/ global se volvió unmaintainable?
Wrappers de un solo uso se acumulan porque "components/" se siente como el lugar correcto para cualquier archivo JSX. Por defecto a componentes específicos de características; promociona a ui/ solo con un segundo consumidor real.
¿Cómo interactúa esto con contenedor/presentador?
features/<name>/screens/ contiene contenedores (datos, navegación). features/<name>/components/ contiene presentadores (UI solo props). Ver Contenedor/Presentador en Mobile.
¿Expo Router requiere features/?
No - Router solo requiere un directorio app/. features/ es una convención organizacional que mantiene las rutas delgadas e implementación testeable.
¿Cómo funcionan alias de ruta como @/*?
Las plantillas de Expo incluyen tsconfig.json con "paths": { "@/*": ["./*"] } o "./src/*". Apunta @/ a src/ de modo que @/features/orders se resuelve consistentemente. Metro respeta los mismos paths cuando se configura vía expo/tsconfig o babel-plugin-module-resolver si es necesario.
¿Y las pruebas - colocar o carpeta __tests__?
Coloca OrderRow.test.tsx junto a OrderRow.tsx dentro de la carpeta de características. Las pruebas de integración de características viven en features/orders/__tests__/ si prefieres agrupar. Mantén las pruebas dentro del slice que estás probando.