|
| 1 | +# Mejores Prácticas |
| 2 | + |
| 3 | +Patrones listos para producción, tips de rendimiento y estrategias de testing para aplicaciones `watch_it`. |
| 4 | + |
| 5 | +## Patrones de Arquitectura |
| 6 | + |
| 7 | +### Widgets Auto-Contenidos |
| 8 | + |
| 9 | +Los widgets deberían acceder a sus dependencias directamente desde `get_it`, no vía parámetros del constructor. |
| 10 | + |
| 11 | +**❌️ Malo - Pasar managers como parámetros:** |
| 12 | + |
| 13 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#passing_managers_bad |
| 14 | + |
| 15 | +**✅ Bueno - Acceder directamente:** |
| 16 | + |
| 17 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#access_directly_good |
| 18 | + |
| 19 | +**¿Por qué?** Los widgets auto-contenidos son: |
| 20 | +- Más fáciles de testear (mock de `get_it`, no parámetros del constructor) |
| 21 | +- Más fáciles de refactorizar |
| 22 | +- No exponen dependencias internas |
| 23 | +- Pueden acceder a múltiples servicios sin explosión de parámetros |
| 24 | + |
| 25 | +### Separar Estado de UI del Estado de Negocio |
| 26 | + |
| 27 | +**Estado de UI local** (entrada de formulario, expansión, selección): |
| 28 | + |
| 29 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#local_ui_state |
| 30 | + |
| 31 | +**Estado de negocio** (datos de API, estado compartido): |
| 32 | + |
| 33 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#business_state |
| 34 | + |
| 35 | +### Estado Reactivo Local con createOnce |
| 36 | + |
| 37 | +Para estado reactivo local del widget que no necesita registro en `get_it`, combina `createOnce` con `watch`: |
| 38 | + |
| 39 | +<<< @/../code_samples/lib/watch_it/watch_create_once_local_state.dart#example |
| 40 | + |
| 41 | +**Cuándo usar este patrón:** |
| 42 | +- El widget necesita su propio estado reactivo local |
| 43 | +- El estado debería persistir a través de reconstrucciones (no recreado) |
| 44 | +- El estado debería ser dispuesto automáticamente con el widget |
| 45 | +- No quieres registrar en `get_it` (verdaderamente local) |
| 46 | + |
| 47 | +**Beneficios clave:** |
| 48 | +- `createOnce` crea el notifier una vez y lo dispone automáticamente |
| 49 | +- `watch` se suscribe a cambios y dispara reconstrucciones |
| 50 | +- No se necesita gestión manual de ciclo de vida |
| 51 | + |
| 52 | +## Optimización de Rendimiento |
| 53 | + |
| 54 | +### Observa Solo Lo Que Necesitas |
| 55 | + |
| 56 | +Observa propiedades específicas, no objetos enteros. El enfoque depende de la estructura de tu manager: |
| 57 | + |
| 58 | +**Para managers con propiedades ValueListenable** - usa `watchValue()`: |
| 59 | + |
| 60 | +**❌️ Malo - Observar manager completo:** |
| 61 | + |
| 62 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#watching_too_much_bad |
| 63 | + |
| 64 | +**✅ Bueno - Observar propiedad ValueListenable específica:** |
| 65 | + |
| 66 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#watch_specific_good |
| 67 | + |
| 68 | +**Para managers ChangeNotifier** - usa `watchPropertyValue()` para reconstruir solo cuando un valor de propiedad específica cambia: |
| 69 | + |
| 70 | +**❌️ Malo - Se reconstruye en cada llamada notifyListeners:** |
| 71 | + |
| 72 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#rebuilds_on_every_settings_bad |
| 73 | + |
| 74 | +**✅ Bueno - Se reconstruye solo cuando el valor de darkMode cambia:** |
| 75 | + |
| 76 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#rebuilds_only_darkmode_good |
| 77 | + |
| 78 | +### Dividir Widgets Grandes |
| 79 | + |
| 80 | +No observes todo en un widget gigante. Divide en widgets más pequeños que observen solo lo que necesitan. Esto asegura que solo los widgets más pequeños se reconstruyan cuando sus datos cambien. |
| 81 | + |
| 82 | +**❌️ Malo - Un widget observa todo:** |
| 83 | + |
| 84 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#one_widget_watches_everything_bad |
| 85 | + |
| 86 | +**✅ Bueno - Cada widget observa sus propios datos:** |
| 87 | + |
| 88 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#split_widgets_good_dashboard |
| 89 | + |
| 90 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#split_widgets_good_user_header |
| 91 | + |
| 92 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#split_widgets_good_todo_list |
| 93 | + |
| 94 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#split_widgets_good_settings_panel |
| 95 | + |
| 96 | +### Constructores Const |
| 97 | + |
| 98 | +::: tip Usa const con tus watching widgets |
| 99 | +Los constructores const funcionan con todos los tipos de widget de `watch_it`: `WatchingWidget`, `WatchingStatefulWidget`, y widgets usando `WatchItMixin`. Flutter puede optimizar widgets const para mejor rendimiento de reconstrucción. |
| 100 | +::: |
| 101 | + |
| 102 | +## Testing |
| 103 | + |
| 104 | +### Testea Lógica de Negocio Por Separado |
| 105 | + |
| 106 | +Mantén tu lógica de negocio (managers, servicios) separada de los widgets y testéala independientemente: |
| 107 | + |
| 108 | +**Unit test del manager:** |
| 109 | +```dart |
| 110 | +test('TodoManager filters completed todos', () { |
| 111 | + final manager = TodoManager(); |
| 112 | + manager.addTodo('Task 1'); |
| 113 | + manager.addTodo('Task 2'); |
| 114 | + manager.todos[0].complete(); |
| 115 | +
|
| 116 | + expect(manager.completedTodos.length, 1); |
| 117 | + expect(manager.activeTodos.length, 1); |
| 118 | +}); |
| 119 | +``` |
| 120 | + |
| 121 | +**Sin dependencias de Flutter = tests rápidos.** |
| 122 | + |
| 123 | +### Testea Widgets con Dependencias Mockeadas |
| 124 | + |
| 125 | +Para widget tests, usa scopes para aislar dependencias. **Crítico:** Debes registrar cualquier objeto que tu widget observe ANTES de llamar a `pumpWidget`: |
| 126 | + |
| 127 | +```dart |
| 128 | +testWidgets('TodoListWidget displays todos', (tester) async { |
| 129 | + // Usa un scope para aislamiento de tests |
| 130 | + await GetIt.I.pushNewScope(); |
| 131 | +
|
| 132 | + // Registra mocks ANTES de pumpWidget |
| 133 | + final mockManager = MockTodoManager(); |
| 134 | + when(mockManager.todos).thenReturn([ |
| 135 | + Todo('Task 1'), |
| 136 | + Todo('Task 2'), |
| 137 | + ]); |
| 138 | + GetIt.I.registerSingleton<TodoManager>(mockManager); |
| 139 | +
|
| 140 | + // Ahora crea el widget |
| 141 | + await tester.pumpWidget(MaterialApp(home: TodoListWidget())); |
| 142 | +
|
| 143 | + expect(find.text('Task 1'), findsOneWidget); |
| 144 | + expect(find.text('Task 2'), findsOneWidget); |
| 145 | +
|
| 146 | + // Limpiar scope |
| 147 | + await GetIt.I.popScope(); |
| 148 | +}); |
| 149 | +``` |
| 150 | + |
| 151 | +**Puntos clave:** |
| 152 | +- **Registra objetos observados ANTES de `pumpWidget`** - el widget intentará acceder a ellos durante la primera construcción |
| 153 | +- Usa `pushNewScope()` para aislamiento de tests en lugar de `reset()` |
| 154 | +- El widget accede a mocks vía `get_it` automáticamente |
| 155 | +- Los widgets auto-contenidos son más fáciles de testear - no se necesitan parámetros del constructor |
| 156 | + |
| 157 | +Para estrategias comprehensivas de testing con `get_it`, ver la [Testing Guide](/documentation/get_it/testing.md). |
| 158 | + |
| 159 | +## Organización del Código |
| 160 | + |
| 161 | +### Estructura del Widget |
| 162 | + |
| 163 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#widget_structure |
| 164 | + |
| 165 | +## Anti-Patrones |
| 166 | + |
| 167 | +### ❌️ No Accedas a `get_it` en Constructores |
| 168 | + |
| 169 | +**❌️ Malo - Acceder en el constructor:** |
| 170 | + |
| 171 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#dont_access_getit_constructors_bad |
| 172 | + |
| 173 | +**✅ Bueno - Usa callOnce:** |
| 174 | + |
| 175 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#dont_access_getit_constructors_good |
| 176 | + |
| 177 | +**¿Por qué?** Los constructores se ejecutan antes de que el widget esté adjunto al árbol, y se llamarán nuevamente cada vez que el widget se recree. Usa `callOnce()` para asegurar que la inicialización suceda solo una vez cuando el widget realmente se construya. |
| 178 | + |
| 179 | +### ❌️ No Violes Reglas de Orden de Watch |
| 180 | + |
| 181 | +::: warning El Orden de Watch es Crítico |
| 182 | +Todas las llamadas `watch*`, `callOnce`, `createOnce`, y `registerHandler` deben estar en el mismo orden en cada construcción. Esta es una restricción fundamental del diseño de `watch_it`. |
| 183 | + |
| 184 | +Ver [Watch Ordering Rules](/documentation/watch_it/watch_ordering_rules.md) para detalles completos y excepciones seguras. |
| 185 | +::: |
| 186 | + |
| 187 | +### ❌️ No Hagas Await de Commands |
| 188 | + |
| 189 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#dont_await_execute_bad_anti |
| 190 | + |
| 191 | +<<< @/../code_samples/lib/watch_it/best_practices_patterns.dart#dont_await_execute_good_anti |
| 192 | + |
| 193 | +### ❌️ No Pongas Llamadas Watch en Callbacks |
| 194 | + |
| 195 | +Ver [Watch Ordering Rules](/documentation/watch_it/watch_ordering_rules.md) - las llamadas watch deben estar en build(), no en callbacks. |
| 196 | + |
| 197 | +## Debugging |
| 198 | + |
| 199 | +Habilita tracing con `enableTracing()` o `WatchItSubTreeTraceControl` para entender el comportamiento de reconstrucción. Para técnicas de debugging detalladas y resolución de problemas comunes, ver [Debugging & Troubleshooting](/documentation/watch_it/debugging_tracing.md). |
| 200 | + |
| 201 | +## Ver También |
| 202 | + |
| 203 | +- [Watch Ordering Rules](/documentation/watch_it/watch_ordering_rules.md) - Restricciones CRÍTICAS |
| 204 | +- [Debugging & Troubleshooting](/documentation/watch_it/debugging_tracing.md) - Problemas comunes |
| 205 | +- [Observing Commands](/documentation/watch_it/observing_commands.md) - Integración con command_it |
| 206 | +- [Testing](/documentation/get_it/testing.md) - Testing con `get_it` |
0 commit comments