Commit 4ab76811 by Miguel Mejía

Adds the documentation to work in frontend

parent 964f7b04
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
- [Tareas (tickets)](sections/tickets.md) 🎫 - ¡Aprende cómo gestionamos nuestras tareas! 📝 Este documento te mostrará cómo utilizamos los tickets para organizar y priorizar nuestro trabajo. - [Tareas (tickets)](sections/tickets.md) 🎫 - ¡Aprende cómo gestionamos nuestras tareas! 📝 Este documento te mostrará cómo utilizamos los tickets para organizar y priorizar nuestro trabajo.
- [Versionamiento de código fuente](sections/code-versioning.md) 💾 - ¡Domina el control de versiones! 💪 Este documento te enseñará cómo versionamos nuestro código fuente para mantener un historial de cambios y colaborar de manera segura. - [Versionamiento de código fuente](sections/code-versioning.md) 💾 - ¡Domina el control de versiones! 💪 Este documento te enseñará cómo versionamos nuestro código fuente para mantener un historial de cambios y colaborar de manera segura.
- [Proyectos back end](sections/backend-structure.md) ⚙️ - ¡Definamos como será la estructura de proyectos back end! - [Proyectos back end](sections/backend-structure.md) ⚙️ - ¡Definamos como será la estructura de proyectos back end!
- [Proyectos front-end](sections/frontend-structure.md) 💅 - Hagamos del front-end un lugar feliz y seguro :D
## Secciones pendientes 🚧 ## Secciones pendientes 🚧
...@@ -20,4 +21,5 @@ ...@@ -20,4 +21,5 @@
- [Bases de datos relacionales](sections/relational-dbs.md) 📊 - ¡Asegúrate de que tus bases de datos sean ordenadas, eficientes y seguras! - [Bases de datos relacionales](sections/relational-dbs.md) 📊 - ¡Asegúrate de que tus bases de datos sean ordenadas, eficientes y seguras!
--- ---
* volver al [inicio](/README.md)
- volver al [inicio](/README.md)
# 1. Introducción
Partimos de una idea fundamental: escribimos código no para que una máquina haga algo, sino para que otra persona entienda que se le pidió a la máquina que hiciera.
Aunque el rendimiento y la optimización son clave, la sostenibilidad del código es lo que debe guiar el cómo lo escribimos, y esta sostenibilidad está basada en la legibilidad y qué tan rápido una persona que no está familiarizada con el _codebase_ es capaz de comprenderlo. Es bonito cuando logramos algo con dos o tres líneas de código, pero si esas líneas parecen una validación de RegEx con look-ahead, nuestro momento HackerMan va a ser la miseria del equipo al mediano y largo plazo.
Hay miles de formas de escribir lo mismo en JavaScript/TypeScript y React, y no siempre estaremos disponibles para explicar nuestro razonamiento. Incluso, es muy probable que en una semana olvidemos por qué escribimos X y no Y. Por eso, es esencial unificar la forma en la que nos expresamos a través del código... Además, simpre será bonito cuando reducimos la carga cognitiva de las cosas.
Este documento es nuestro acuerdo para hablar el mismo idioma, ayudando a que cualquier persona que lea nuestro código se sienta familiarizada y pueda contribuir sin obstáculos.
---
[Volver al menú principal](../frontend-structure.md) | [Siguiente: Configuración del Entorno](./2-configuracion-del-entorno.md)
## 2. Configuración del Entorno
**TODO LO QUE HACEMOS DEBE ESTAR EN TYPESCRIPT**
### Herramienta de Formato
Para que el código de los proyectos en JS/TS/React/CualquierCompliantConEcmaScript tenga siempre el mismo formato sin importar quién lo escriba, usamos Prettier. Prettier es una herramienta opinionada, lo que significa que tiene su propio criterio para el formato del código, y como equipo, decidimos adoptarlo y respetarlo al pie de la letra
Aquí no hay debate: si a Linus Torvalds le gusta la indentación de 8 espacios y a ti te gusta la de 4, pero Prettier dice que 2, nos vamos con 2. No hay discusión, Prettier es palabra sagrada. Esto nos libera de discusiones sobre estilo y nos permite enfocarnos en lo que importa.
Puedes integrarlo en tu editor con los plugins oficiales. Si no, de seguro habremos dispuesto de un script dentro del package.json para darle formato al código: usualmente `<npm/pnpm/yarn/administrador de paquetes> format` harán ese trabajo.
### La seguridad antes que la policía
Prettier es nuestra brújula de estilo y ESLint es quien evita que la caguemos antes de tiempo con pendejadas. Su propósito es detectar problemas de calidad, consistencia y posibles errores antes de que lleguen al código. Nuestra configuración combina las recomendaciones estándar de Airbnb, las de react y la ultima palabra la da prettier para evitar conflictos en las reglas...
```typescript
extends: [
"airbnb",
"airbnb-typescript",
"plugin:react-hooks/recommended",
"plugin:prettier/recommended",
],
```
Pero en esta área nuestro equipo si tiene opiniones y acuerdos que modifican las reglas base y responden a el cómo vemos el código y las experiencias que hemos tenido.
#### Algunas de nuestras reglas clave
**Evitamos la anidación excesiva**. La regla `no-nested-ternary` nos prohíbe usar operadores ternarios dentro de otros, ya que reducen la legibilidad y aumentan la carga cognitiva.
**Corchetes, siempre**, `curly` nos obliga a usar llaves en todos los bloques de control, como en los `if` y `for`. Así, evitamos errores al añadir más líneas de código en el futuro.
**¡Exportaciones nombradas, siempre!** Con `import/no-default-export` nos aseguramos de que cada archivo solo tenga exportaciones con nombre. Esto facilita el autocompletado y reduce los conflictos de nombres.
**En React, componentes declarativos, funciones internas de flecha,** los componentes de React deben ser declaraciones de función (`function Componente() {}`), lo que ayuda a la legibilidad y la depuración. Sin embargo, para callbacks y funciones internas del componente, preferimos las funciones flecha (`() => {}`) por su sintaxis concisa y su manejo más predecible de `this`.
**En TypeScript...** funciones de flecha, para evitarnos dolores de cabeza con el this, aunque sea más idiomática function.
#### Nuestro ruleset
```typescript
"rules": {
// === REGLAS DE REACT Y HOOKS ===
// Desactiva la regla que requiere importar 'React' en cada archivo con JSX. Esto es estándar en React 17+.
"react/react-in-jsx-scope": "off",
// Desactiva la restricción de 'prop spreading' (ej. <Componente {...props} />).
"react/jsx-props-no-spreading": "off",
// Define el estilo de los componentes: `function declaration` para los nombrados y `arrow function` para los anónimos.
"react/function-component-definition": [
"error",
{
"namedComponents": "function-declaration",
"unnamedComponents": "arrow-function"
}
],
// === REGLAS DE IMPORTACIÓN ===
// Evita ciclos de importación (dependencias circulares).
"import/no-cycle": "error",
// Asegura que las importaciones de módulos locales no tengan extensión de archivo.
"import/extensions": [
"error",
"ignorePackages",
{
"ts": "never",
"tsx": "never",
"js": "never",
"jsx": "never"
}
],
// Prohíbe todas las exportaciones 'default' en la base de código.
"import/no-default-export": "error",
// Desactiva la regla que preferiría exportaciones default de Airbnb.
"import/prefer-default-export": "off",
// Prohíbe las importaciones usando rutas relativas ('../') para forzar el uso de rutas absolutas desde 'src/' y alias.
"import/no-relative-parent-imports": "error",
// Solo permite dependencias de desarrollo en archivos de configuración y test.
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": [
"**/*.test.{ts,tsx}",
"**/*.spec.{ts,tsx}",
"vite.config.ts",
"jest.config.{js,ts}"
]
}
],
// === REGLAS DE TYPESCRIPT ===
// Prefiere la sintaxis concisa para los tipos de función.
"@typescript-eslint/prefer-function-type": "error",
// Muestra un error si una variable no es usada, ignorando las que empiezan con un guion bajo '_'.
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_"
}
],
// Prefiere las importaciones de tipo (`import type`) para claridad.
"@typescript-eslint/consistent-type-imports": [
"error",
{
"prefer": "type-imports"
}
],
// === REGLAS DE ESTILO Y LÓGICA GENERAL ===
// Define el estilo de las funciones.
"func-style": [
"error",
"declaration",
{
"allowArrowFunctions": true
}
],
// Obliga a usar `arrow functions` para callbacks.
"prefer-arrow-callback": [
"error",
{
"allowNamedFunctions": true,
"allowUnboundThis": true
}
],
// Prohíbe el uso de operadores ternarios anidados.
"no-nested-ternary": "error",
// Obliga a usar llaves en todos los bloques de control
"curly": "error",
// Permite `console.warn` y `console.error` pero no `console.log`.
"no-console": [
"warn",
{
"allow": ["warn", "error"]
}
],
// Prohíbe `var` y exige `const` o `let`.
"no-var": "error",
// Exige `const` si la variable no se reasigna.
"prefer-const": "error"
}
}
```
---
[Anterior: Introducción](./1-introduccion.md) | [Volver al menú principal](../frontend-structure.md) | [Siguiente: Convenciones Generales](./3-convenciones-generales.md)
\ No newline at end of file
# 3. Convenciones Generales
## Orden del código
**Módulos**: Siempre trabajaremos con el sistema de Módulos de ECMAScript (ESM) en nuestro código. Aunque aún existen librerías en CommonJS (CJS), podemos usarlas como módulos e importarlas sin usar la sintaxis de `require`.
**Evitar el entorno global**: Usemos las capacidades de los módulos para mantener nuestro entorno global limpio. No hay necesidad de declarar variables globales que ensucien todo y generar conflictos.
- **Clases con propósito**: No creemos clases solo para encapsular un conjunto de variables y funciones, eso lo puede hacer mejor un módulo. Las clases deben cumplir un propósito de arquitectura, como implementar un patrón de diseño (di tu un _singleton_) o manejar un estado interno complejo y persistente.
## Una línea, una acción
Buscamos que cada línea de código tenga un propósito único y fácil de entender. Cuando una sola línea hace demasiadas cosas, se vuelve difícil de leer y peor de depurar. Por eso, preferimos la claridad sobre la concisión.
Esto explica porqué evitamos los ternarios anidados y usamos llaves en todos nuestros bloques de control (incluso en los `if` de una sola línea), lo que hace que el código sea predecible y reduce los errores a largo plazo.
## Nomenclatura
Una de las cosas más difíciles en la programación es asignar buenos nombres de forma consistente. La razón es simple: un buen nombre reduce la necesidad de explicaciones. Buscamos nombres que le digan a cualquiera lo que están viendo sin necesidad de comentarios.
- **Idioma**: Aunque respetamos el idioma en el que hablamos, el "espanglish" puede ser muy molesto y aumenta la carga cognitiva. Dado que los lenguajes de programación que usamos están en inglés, hemos acordado usar este idioma en nuestro código.
- **Variables y funciones**: Usamos **`camelCase`**. Los nombres de variables deben ser sustantivos que describan su contenido (ej., `userId`, `userName`), y los de las funciones, verbos que indiquen lo que hacen (ej., `fetchUsers`, `calculateTotal`, `getSomeShit`)... tener cuidado con palabras genéricas como `info` o `data` y de ser posible optar por algo más descriptivo de la información que contienen.
- **Tipos, clases y componentes**: Usamos **`PascalCase`**. Esto ayuda a diferenciarlos del resto del código (ej., `UserType`, `ProductCard`).
- **Constantes de configuración**: Usamos **`SCREAMING_SNAKE_CASE`**. Son constantes que se usan para una configurar un comportamiento del código, su nombre en mayúsculas debe dejar muy claro la info que contienen (ej., `API_URL`, `MAX_RETRIES`, `I_AM_A_MAGIC_NUMBER`).
## Librerías Externas
Importar debe suceder solo cuando algo es realmente necesario. Evitamos importar por importar y nos aseguramos de que las librerías que usamos estén bien mantenidas y sean de alta calidad.
Un ejemplo de lo que puede salir mal es el caso de **`left-pad`**. Una librería con una función simple (no más de cinco líneas de código), que, al ser eliminada de NPM(Luego de eso se reforzó la política de no borrado de NPM), causó que miles de proyectos dejaran de compilar. [El chisme completo](https://en.wikipedia.org/wiki/Npm_left-pad_incident).
**Cada dependencia es una responsabilidad.**
Por lo tanto:
- Solo importamos cuando la lógica del proyecto no depende de la importación.
- Solo importamos si hemos confirmado que no hay una forma directa o clara de realizar lo que necesitamos con nuestro propio código dentro del tiempo que disponemos.
- Solo importamos librerías que tengan soporte activo (al menos un año).
- Solo importamos librerías con tipado en TypeScript nativo para integrarlas mejor.
## Accesibilidad
Construir una interfaz de usuario no es solo cuestión de estética. La accesibilidad es fundamental para que nuestras aplicaciones puedan ser usadas por cualquier persona, considerando sus diversas capacidades físicas, cognitivas, sociales, contextuales y tecnológicas.
**HTML Semántico**: Usa las etiquetas correctas (`<button>`, `<nav>`, `<article>`, ... ) en lugar de `<div>` como mantequilla en restaurante francés. El HTML semántico es clave para construir aplicaciones web accesibles desde el inicio, ayudando a los lectores de pantalla a entender la estructura de la página, facilitando la navegación con tecnologías asistivas y mejorando el SEO. [Aquí hay una pequeña guía](https://webdesign.solomon.ng/html-and-css/semantic-html/semantic-html.html).
**Atributos `aria-*`**: Una vez que nuestro documento está bien estructurado, los usuarios con tecnologías asistivas deben ser capaces de comprender los cambios que introducimos en el documento a través de las interacciones. Aquí es donde entra `aria-*`, que complementa lo que el HTML no cubre. Sin embargo, _no aria is better than wrong aria_(Es mejor no tener aria a tener uno mal escrito). Si no entiendes el atributo, investiga antes de usarlo. [Esta es una buena introducción a ARIA](https://accessibilityfordevelopers.com/guide-to-aria/) y [aquí está la guía de MDN](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA), que es más amigable que las especificaciones de la W3C.
**Manejo de Foco**: Es clave que todo lo que se puede hacer con el _mouse_ también se posible hacerlo con el teclado. Asegúrate de que los elementos sean enfocables y que el orden del foco sea lógico.
## El texto de la interfaz de usuario
Para facilitar el soporte, la internacionalización y las actualizaciones, se debe evitar "quemar" el texto directamente en el código. La práctica preferida es guardarlo en un archivo JSON, en la última carpeta común que use este texto, o al mismo nivel del directorio donde es usado. Así, tenemos una fuente única y consistente para el texto usado por el padre y todos sus hijos o todos los hermanos, y cualquier cambio de texto sucede en un solo lugar sin tener que modificar el código funcional.
## Comentarios solo cuando el código no lo explica todo
Idealmente, los nombres y la estructura de nuestro código deberían ser tan claros que hagan de los comentarios algo innecesario. Sin embargo, rara vez nuestro código alcanza ese nivel de "iluminación", y necesitamos explicar el _porqué_ de una solución. Comentemos lo menos posible y, cuando lo hagamos, sigamos estos parámetros:
- **Solo lo esencial**: Los comentarios deben usarse para explicar la razón de una decisión, no lo que el código está haciendo.
- **Etiquetas**: Usar etiquetas estandarizadas ayuda a que los comentarios sean fáciles de encontrar y den contexto a quien los lee en el futuro:
- **`NOTE:`**: Para dejar una nota importante.
- **`WARN:`**: Para advertir de un posible problema.
- **`HACK:`**: Para explicar soluciones temporales que deben ser atendidas pronto.
- **`TODO:`**: Para marcar una tarea pendiente.
- **`FIX:`**: Para indicar que algo está roto y necesita ser arreglado.
- **Documentación (TSDoc)**: Úsalo solo para funciones y métodos que se exportan y son usados por otros archivos. Como regla general, piensa en qué información te gustaría tener disponible en las ventanas del LSP de tu editor de texto cuando escribes código. No es necesario explicar una función que solo es usada dentro del mismo archivo.
## Aduana: Importaciones y exportaciones
El orden de las importaciones es crucial para la legibilidad y una estructura consistente en todo nuestro codigo, el orden acordado es, primero las **librerías externas** y dependencias de `node_modules`, como `leaflet` y `axios`, seguidas por los **módulos internos**, que son los archivos de nuestro _codebase_, lo que hay dentro de `src/`, estos últimos siempre deben ser importados con rutas absolutas o alias.
Además, como equipo, evitamos las exportaciones `default` para tener un mejor seguimiento de los elementos. Esto nos asegura que siempre importemos con el nombre exacto (`import { Button } from '...'`), o que si cambiamos el nombre, sea de forma consciente, evitando confusiones.
### Importaciones Dinámicas
Evitamos las importaciones dinámicas (`import('someShit')`) a menos que haya una razón clara para hacerlo, como la optimización, rendimiento o _code splitting_, y en estos casos debe estar documentada la razón. Implementarla sin atención puede complicar el seguimiento de los módulos.
---
[Volver al menú principal](../frontend-structure.md) | [Siguiente: Buenas Prácticas en TypeScript](./4-buenas-practicas-en-typescript.md)
# 4. Buenas Prácticas en TypeScript
Sí, TypeScript es un linter glorificado, pero es el que nos permite movernos de forma segura dentro del despelote que armó el uso desmedido de JavaScript en la web y las ganas de meterlo hasta en la cafetera. No es solo una barrera de seguridad, es una herramienta para construir aplicaciones más robustas y fáciles de entender.
## Explicar lo suficiente
TypeScript puede inferir tipos por sí solo, pero no siempre es la mejor práctica. En algunos casos, el tipado explícito es fundamental para la claridad y la legibilidad.
**Tipado inferido**: Lo usamos en casos obvios, donde el tipo es claro a simple vista (ej., `const name = 'Andrés'`). A menudo, los tipos inferidos pueden ser más precisos que los explícitos. En este caso, el tipo de `name` sería la cadena literal `'Andrés'`, que es más precisa que `string`.
**Tipado explícito**: Es esencial para funciones, propiedades de objetos o variables que podrían tener más de un tipo (ej., `const data: string[] | number[]`). También lo usamos cuando el tipo no es tan fácil de inferir, como en un objeto literal, o cuando sabemos que el valor va a ser asignado por un desarrollador, para no correr el riesgo que un cambio de tipo `42` a `"42"` nos rompa el programa. Este tipado explícito nos ayuda a documentar el código sin necesidad de más comentarios.
**Retornos Tipados**: Aunque TypeScript puede inferir muy bien el tipo de retorno, hacer explícitos los tipos del retorno en la firma de la función, ayuda a dar una idea clara de lo que hace antes de analizar su funcionamiento interno.
## Interfaces y Tipos: ¿Cuál y cuándo?
La elección entre `interface` y `type` puede ser confusa, y cada 8 días puede cambiar la forma en la que uno tiparía un objeto, pero generalmente...
**Type**: Es el la declaración base, es la más flexible y te permite crear uniones (`|`), intersecciones (`&`) y definir tipos primitivos o literales, al igual que objetos.
**Interface**: Su uso es más cercano a las Clases por su posibilidad de ser extensible, lo cual nos puede servir para crear jerarquías de tipos.
## Arrays y Genéricos
**Tipado de _arrays_**: Preferimos la sintaxis `string[]` en lugar de `Array<string>`. Es más concisa, fácil de leer y la convención más común en la comunidad.
**Construcción de genéricos**: Úsalos para crear componentes o funciones que pueden trabajar con una variedad de tipos de datos sin perder el tipado. Un buen genérico no solo tipa la entrada, sino que también tipa la salida, asegurando que la información que retorna la función sea del mismo tipo que recibió.
```typescript
// Mal ejemplo: usa 'any' y pierde el tipado del array.
function getItem(array: any[], index: number): any {
return array[index];
}
// Buen ejemplo: usa un tipo genérico 'T' para mantener el tipado.
// La función `getItem` siempre devolverá un elemento del mismo tipo que recibió.
function getItem<T>(array: T[], index: number): T {
return array[index];
}
const names = ['Andrés', 'Sofía', 'Pedro'];
const aName = getItem(names, 1); // aName es de tipo string, no 'any'.
```
## Archivos de definición de tipos
Para mantener el proyecto organizado, el alcance de los tipos es crucial. Cuando un tipo tiene un alcance limitado a un solo archivo, lo declaramos en ese mismo archivo. Esto mantiene el código cohesivo.
Si un tipo se usa en múltiples archivos, lo extraemos a un archivo `*.types.ts` en la carpeta más cercana que lo contenga. Esta práctica asegura que tengamos una única fuente de verdad, lo que mejora la consistencia y la sostenibilidad del código.
## Vamo' a calmarno'
Typescript es un linter, no vale la pena pasar más tiempo construyendo tipos perfectos que escribiendo la lógica de lo que estamos trabajando. Si una solución simple funciona, usarla sin miedo. Las abstracciones innecesarias y la "masturbación de tipos" solo añaden confusión y complican el código.
## Constantes con enums y posibilidades con literales
Para mantener el código ordenado, siempre que sea posible podemos agrupar el tipado de contantes relacionadas en enums
```typescript
enum Status {
Loading = 'LOADING',
Success = 'SUCCESS',
Error = 'ERROR',
}
const currentStatus = Status.Loading;
```
El uso de literales es recomendado para un conjunto de valores fijos y acotados. Por ejemplo: `type UserRole = 'admin' | 'guest'`.
## `any`, `unknown` y `never`
**`any`**: **NO USARLO NUNCA**, salvo que estemos en proceso de migrar algo de JS a TS. Es la peor práctica y anula completamente el propósito de TypeScript.
**`unknown`**: Este es el tipo de dato que debes usar cuando no sabes qué tipo va a entrar. Después, usa _type narrowing_ para trabajar con el valor.
**`never`**: Se usa para indicar que una función **nunca** retorna un valor (ej., una función que siempre lanza un error o entra en un bucle infinito).
## Helpers y Tipos Utilitarios
**`type guards`**: Hacer uso de estas funciones que validan y reducen el tipo de un valor. Suelen tener esta firma: `(myValue: unknown): myValue is Type`. Son cruciales para trabajar con datos que no tienen un tipo seguro.
**`Partial`, `Pick`, `Omit`, and shit...**: Los tipos utilitarios de TypeScript son poderosos. En lugar de crear un tipo nuevo cada vez que necesitas una variante de un tipo ya existente, usar utilitarios.
---
[Volver al menú principal](../frontend-structure.md) | [Siguiente: Buenas Prácticas en React](./5-buenas-practicas-en-react.md)
# 5. Buenas Prácticas en React
## Componentes de Función: La preferencia del equipo
No hay razón para seguir escribiendo componentes de clase, son una tecnología vieja que se evitamos en nuestras aplicaciones. Los componentes de función, junto con los _Hooks_, ofrecen una forma más simple y directa de manejar el estado, la sincronización y los efectos secundarios, lo que resulta en un código más limpio y fácil de depurar.
## Estructura y Organización
Los componentes deben tener una única responsabilidad. Los componentes que orquestan, renderizan y hacen malabares al mismo tiempo, fácilmente se convierten en nidos de _bugs_, como condiciones de carrera o bucles de renderizado.
la estructura de los componentes en el archivodebe ser la siguiente:
```tsx
import { useContext, useMemo, useState, useCallback } from "react";
import { theAppContext } from "AppContext";
// 1. Declaracion de tipos locales y constantes importantes para el componente o que determinan el funcionamiento del componente
interface MyComponentProps {
data: number[];
}
const demonNames: Map<number, string> = new Map([
[1, "Asmodeus"],
[2, "Belial"],
[3, "Baphomet"],
[4, "Belzebub"],
// ...
]);
// 2. Declaracion del componente
export default function MyComponent({ data }: MyComponentProps) {
// a. Hooks, constantes y callbacks memoizados
const [myState, setMyState] = useState<string>("Some Shit");
const myContext = useContext(theAppContext);
const expensiveCalculation = useMemo(() => {
return data.length * myContext.counter;
}, [data, myContext.counter]);
// b. Lógica interna, callbacks no memoizados y funciones que dependan del estado, props o hooks
const handleClick = () => {
setMyState("This is the new Shit");
};
// c. Variables derivadas para el render
const otherShit = myState !== "Some Shit";
const baphyApproved = otherShit && expensiveCalculation === 666;
// d. Render
return (
<div>
<p>{myState}</p>
{otherShit && <p>{expensiveCalculation}</p>}
{baphyApproved && <p>Beast time!!!</p>}
{baphyApproved &&
data.map((id) => (
<p key={id}>{idToDemonName(id)}</p>
))}
<button onClick={handleClick}>Change this shit</button>
</div>
);
}
// 3. helpers y funciones relacionadas con el componente que no dependen del estado, props o hooks de este
function idToDemonName(id: number): string {
const name = demonNames.get(id) ?? "Unknown Demon";
return `${id} is ${name}...`;
}
```
### El `return` claro y el chocolate oscuro
El JSX no es entorno amable para leer, por lo que el `return` debe ser lo más limpio posible. Toda la lógica compleja como los cálculos de estado, la preparación de datos, las condiciones de renderizado y los _handlers_ debe hacerse antes del `return`.
Por una cuestión de claridad y consistencia, evitamos los `early returns` en los componentes y centralizar la lógica de lo que se renderiza o no al final del componente.
El `return` debe contener únicamente:
- Expresiones simples (ej., `{condición && <Componente />}`).
- Listas (`.map()`).
- Estructura clara del JSX.
## Props: Cómo pasar y gestionar propiedades
Una de las mayores ventajas de usar TypeScript es que podemos definir la "forma" de las _props_ explícita,emte, lo que nos permite saber exactamente qué tipo de datos estamos recibiendo y enviando, y evitar errores en tiempo de compilación. Siempre tipamos los props. Esta práctica nos asegura que los datos que pasan entre componentes sean consistentes y predecibles.
Un punto importante es evitar el uso de `React.FC` y similares que sirven para tipar componentes. Aunque parece práctico, puede causar problemas con el tipado de la propiedad `children`, siempre la convierte en opcional, sin importar que la hayamos definido como obligatoria o que no la hayamos pasado... lo que podría generar confusiones y _bugs_ difíciles de rastrear.
## Manejo de Estados: Estrategias claras
Di no a los estados derivados; en lugar de almacenar una variable en el estado que se puede calcular a partir de otros, realizamos el cálculo directamente en el cuerpo del componente, antes del renderizado.
Para manejar el estado, usamos `useState` para estados simples, como booleanos, cadenas, números, objetos, etc. Sin embargo, para estados más complejos que involucran múltiples transiciones o modificaciones, preferimos usar `useReducer`, ya que centraliza la lógica de actualización del estado y la hace más predecible y testeable.
## Sincronización (`useEffect`)
Dejemos de pensar en `useEffect` como una simple herramienta para efectos secundarios, tomémoslo como un mecanismo de para la sincronización del componente con sistemas externos (fetch, localstorage, etc). Como se sugiere [You might not need an effect](https://react.dev/learn/you-might-not-need-an-effect), a menudo no se necesita un efecto, por lo que es vital usarlo solo para su propósito real.
Para mantener la claridad y la consistencia, haz que cada `useEffect` se encargue de un solo aspecto. Si un componente necesita múltiples `useEffect`, esto podría ser un indicio de que está asumiendo demasiadas responsabilidades y que la arquitectura debe ser revisada.
Y para cierre del useEffect... si un efecto crea una suscripción o un temporizador, asegúrate de retornar la función de limpieza (_cleanup_) para evitar fugas de memoria y otros chascarrillo.
## useMemo y useCallback
No agarrarlos por default ni meterlos hasta en la sopa. Estos hooks tienen un costo de memoria y de complejidad que no siempre vale la pena. Úsalos solo cuando sea necesario, siguiendo esta idea: empezar sin ellos y agrégalos solo cuando veas un problema de rendimiento o un re-render innecesario que impacte el performance o la UX.
## Código ordenado
Parte de nuestra estrategia para mantener el código ordenado y fácil de depurar, echamos mano de useContext y custom hooks:
**`useContext`** para evitar el "prop-drilling" y datos globales, como el tema de la aplicación, la información del usuario, el estado de autenticación o pasar un reducer para controlar el estado.
**Custom Hooks** para encapsular lógica compleja y reutilizable (como un `useFetchData`), darles una sola responsabilidad, siguiendo el principio de "un solo propósito".
## Fragmentos y Portales
Los **Fragmentos** son un mecanismo de React para agrupar varios elementos hijos sin la necesidad de agregar un nodo extra al DOM, porque aunque la cantidad de nodos en el html importa! y es útil para mantener una estructura semántica y ligera del documento. Usar siempre la sintaxis corta `<>/*contenido del render*/</>`.
`createPortal` nos permiten renderizar un componente fuera del árbol DOM del componente padre. Son muy útiles para crear elementos como modales, _tooltips_ o notificaciones, que necesitan renderizarse en la parte superior del árbol DOM y nos ayudan a evitar problemas de superposición con otros elementos.
## Handlers (Funciones para manejar eventos de usuario)
Para mantener el código claro, un handler debe tener una sola responsabilidad, como hacer una petición a la API o actualizar un estado. Siempre los nombramos con el prefijo handle (ej., handleClick, handleSubmit). Si un handler depende del estado o de las props, lo definimos dentro del componente; si no, lo movemos fuera del componente para que sea más fácil de reutilizar y probar, si solo es usado en este componente, puede dejarse en el mismo archivo, de lo contrario, debe moverse al lugar que la arquitectura de la función haya designado para los helpers y utils.
## Seguridad y Buenas Prácticas
- **Sanitización de datos**: Asegúrate de que los datos externos se limpien antes de ser renderizados en JSX para prevenir ataques de _cross-site scripting_.
- **Manejo seguro de dependencias**: La gestión de dependencias es un proceso delicado. Las actualizaciones automáticas solo deben aplicarse para _hotfixes_ y parches menores. Las actualizaciones de versión y los cambios importantes (_breaking changes_) deben hacerse bajo supervisión y al menos una vez al año.
- **¡No uses `dangerouslySetInnerHTML`!**: Esta propiedad es peligrosa y debe evitarse. Si es absolutamente necesario, su uso debe estar justificado y el contenido debe ser sanitizado de antemano.
## Manejo de errores
### Manejo de Errores Asíncronos
Cada llamada a una API o operación async debe estar envuelta en un bloque try-catch o un chaininng `.then().catch()` apropiado, y los errores deben ser manejados de manera uniforme en toda la aplicación.
Una estrategia son los estados de error en hooks personalizados permiten centralizar la lógica de manejo de errores y reutilizarla en múltiples componentes. Lo que ayuda con la consistencia en cómo se manejan los errores en toda la aplicación y facilita el mantenimiento del código.
```tsx
import { useState, useEffect } from 'react';
function useFetchData(url: string) {
const [data, setData] = useState<any>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error('No se pudo cargar la data.');
}
const result = await response.json();
setData(result);
} catch (err) {
setError('Ocurrió un error al cargar la información.');
}
};
fetchData();
}, [url]);
return { data, error };
}
```
### Validación y Tipos
No podemos confiar en que los datos externos a la aplicación, como los de las APIs, tengan la estructura que esperamos. Por eso, la validación de los datos antes de usarlos en los componentes es clave. Esta validación debe ser explícita y generar errores informativos que nos ayuden a identificar problemas.
```tsx
// Definimos el tipo de dato que esperamos
interface User {
id: number;
name: string;
}
// Usamos una función de validación con una "type guard"
const isUser = (data: unknown): data is User => {
return (
typeof data === 'object' &&
data !== null &&
'id' in data && typeof data.id === 'number' &&
'name' in data && typeof data.name === 'string'
);
};
function MyComponent() {
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch('/api/user/1');
if (!res.ok) {
throw new Error(
`Error en la respuesta de la API: ${res.status}`
);
}
const rawData = await res.json();
if (!isUser(rawData)) {
throw new Error('La API no devolvió la estructura esperada.');
}
// Usamos el dato validado de forma segura
console.log(rawData.name);
} catch (error) {
// Manejamos el error de forma centralizada
console.error(error);
}
};
fetchData();
}, []);
// lo demás del componente
}
```
### Experiencia de Usuario (UX)
La forma en que se comunican los errores es tan importante como la forma en que se manejan. La meta es que incluso cuando las app se rompa, el usuario se sienta en control.
**Interfaces de reserva (_Fallback UI_)**: En lugar de mostrar una pantalla en blanco o un mensaje técnico que el usuario sienta escrito en arameo, debemos proporcionar información clara sobre lo que pasó en una interfaz de reserva.
```tsx
// Cuando el componente falla, se muestra un mensaje claro.
const UserProfile = ({ user }) => {
// ...
return !user ? (
<p>Lo sentimos, no pudimos cargar tu perfil.</p>
) : (
<h1>{user.name}</h1>
{/*...*/}
)
};
```
**Recuperación**: Después de un fallo, ofrecemos formas claras de recuperación. Botones de "reintentar" o instrucciones para volver a la página de inicio son un buen ejemplo de esto. También, para evitar la sensación de que la aplicación está rota, usamos _skeleton screens_ o _spinners_ para indicar los estados de carga. Esto crea una experiencia fluida y profesional.
```tsx
// El botón permite al usuario volver a intentar la operación.
const FetchDataButton = ({ onRetry }) => {
const [error, setError] = useState(false);
// ... lógica para manejar el error
return error ? (
<div>
<p>Ocurrió un error al cargar los datos.</p>
<button onClick={onRetry}>Reintentar</button>
</div>
) : (
{/*...*/}
);
};
```
---
[Volver al menú principal](../frontend-structure.md)
# Guía de estilo para desarrollo en React con TS
## TL;DR
Instale ESLint y Prettier, utilice el config y las reglas de lint suministradas y el default de Prettier.
## Índice de Contenidos
1. [Introducción](./front/1-introduccion.md)
2. [Configuración del Entorno](./front/2-configuracion-del-entorno.md)
- [Herramienta de Formato](./front/2-configuracion-del-entorno.md#herramienta-de-formato)
- [La seguridad antes que la policía](./front/2-configuracion-del-entorno.md#la-seguridad-antes-que-la-policía)
3. [Convenciones Generales](./front/3-convenciones-generales.md)
- [Orden del código](./front/3-convenciones-generales.md#orden-del-código)
- [Una línea, una acción](./front/3-convenciones-generales.md#una-línea-una-acción)
- [Nomenclatura](./front/3-convenciones-generales.md#nomenclatura)
- [Librerías Externas](./front/3-convenciones-generales.md#librerías-externas)
- [Accesibilidad](./front/3-convenciones-generales.md#accesibilidad)
- [El texto de la interfaz de usuario](./front/3-convenciones-generales.md#el-texto-de-la-interfaz-de-usuario)
- [Comentarios solo cuando el código no lo explica todo](./front/3-convenciones-generales.md#comentarios-solo-cuando-el-código-no-lo-explica-todo)
- [Aduana: Importaciones y exportaciones](./front/3-convenciones-generales.md#aduana-importaciones-y-exportaciones)
4. [Buenas Prácticas en TypeScript](./front/4-buenas-practicas-en-typescript.md)
- [Explicar lo suficiente](./front/4-buenas-practicas-en-typescript.md#explicar-lo-suficiente)
- [Interfaces y Tipos: ¿Cuál y cuándo?](./front/4-buenas-practicas-en-typescript.md#interfaces-y-tipos-cuál-y-cuándo)
- [Arrays y Genéricos](./front/4-buenas-practicas-en-typescript.md#arrays-y-genéricos)
- [Archivos de definición de tipos](./front/4-buenas-practicas-en-typescript.md#archivos-de-definición-de-tipos)
- [Vamo' a calmarno'](./front/4-buenas-practicas-en-typescript.md#vamo-a-calmarno)
- [Constantes con enums y posibilidades con literales](./front/4-buenas-practicas-en-typescript.md#constantes-con-enums-y-posibilidades-con-literales)
- [`any`, `unknown` y `never`](./front/4-buenas-practicas-en-typescript.md#any-unknown-y-never)
- [Helpers y Tipos Utilitarios](./front/4-buenas-practicas-en-typescript.md#helpers-y-tipos-utilitarios)
5. [Buenas Prácticas en React](./front/5-buenas-practicas-en-react.md)
- [Componentes de Función: La preferencia del equipo](./front/5-buenas-practicas-en-react.md#componentes-de-función-la-preferencia-del-equipo)
- [Estructura y Organización](./front/5-buenas-practicas-en-react.md#estructura-y-organización)
- [Props: Cómo pasar y gestionar propiedades](./front/5-buenas-practicas-en-react.md#props-cómo-pasar-y-gestionar-propiedades)
- [Manejo de Estados: Estrategias claras](./front/5-buenas-practicas-en-react.md#manejo-de-estados-estrategias-claras)
- [Sincronización (`useEffect`)](./front/5-buenas-practicas-en-react.md#sincronización-useeffect)
- [useMemo y useCallback](./front/5-buenas-practicas-en-react.md#usememo-y-usecallback)
- [Código ordenado](./front/5-buenas-practicas-en-react.md#código-ordenado)
- [Fragmentos y Portales](./front/5-buenas-practicas-en-react.md#fragmentos-y-portales)
- [Handlers (Funciones para manejar eventos de usuario)](./front/5-buenas-practicas-en-react.md#handlers-funciones-para-manejar-eventos-de-usuario)
- [Seguridad y Buenas Prácticas](./front/5-buenas-practicas-en-react.md#seguridad-y-buenas-prácticas)
- [Manejo de errores](./front/5-buenas-practicas-en-react.md#manejo-de-errores)
6. El CSS deja de ser tan feo cuando lo entiendes (guía en desarrollo)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment