Componentes React con JSX y TypeScript

Desarrollo de componentes avanzados con tipado estático

Objetivos de Aprendizaje

Renderizado en React

DOM Virtual
Tipos de Componentes
Sintaxis JSX

Proceso de Renderizado

React utiliza un enfoque innovador para actualizar la interfaz de usuario:

  1. Renderizado Inicial: ReactDOM.render() convierte componentes en nodos del DOM
  2. DOM Virtual: Representación ligera en memoria del árbol de componentes
  3. Re-renderizado: Cuando el estado cambia, se genera un nuevo DOM virtual
  4. Diffing: Compara el nuevo DOM virtual con el anterior
  5. Reconciliación: Actualiza solo las partes necesarias del DOM real

Ejemplo Básico:

Eficiencia del DOM Virtual

El DOM virtual proporciona varias ventajas clave:

  • Rendimiento: Minimiza operaciones costosas en el DOM real
  • Abstracción: Simplifica la programación declarativa
  • Portabilidad: Puede renderizar en diferentes entornos (web, móvil, etc.)

Comparación de Rendimiento:

DOM Directo
DOM Virtual

Tiempo relativo de actualización para 1000 elementos

Componentes de Función

Los componentes funcionales son la forma moderna y preferida de crear componentes en React:

  • Definidos como funciones JavaScript/TypeScript
  • Pueden usar Hooks para manejar estado y efectos
  • Más simples y concisos que los de clase
  • Mejor optimización por el compilador
// Componente funcional con TypeScript
interface GreetingProps {
    name: string;
    age?: number; // Prop opcional
}

const Greeting: React.FC<GreetingProps> = ({ name, age }) => {
    return (
        <div>
            <h1>Hola, {name}!</h1>
            {age && <p>Tienes {age} años</p>}
        </div>
    );
};

Componentes de Clase

Los componentes de clase fueron la forma original de crear componentes con estado:

  • Definidos como clases ES6 que extienden React.Component
  • Usan el método render() para devolver JSX
  • Manejan estado a través de this.state y this.setState()
  • Tienen métodos de ciclo de vida como componentDidMount()
// Componente de clase con TypeScript
interface CounterState {
    count: number;
}

class Counter extends React.Component<{}, CounterState> {
    state: CounterState = { count: 0 };
    
    increment = () => {
        this.setState(prevState => ({
            count: prevState.count + 1
        }));
    };
    
    render() {
        return (
            <div>
                <p>Contador: {this.state.count}</p>
                <button onClick={this.increment}>Incrementar</button>
            </div>
        );
    }
}

Fundamentos de JSX

JSX es una extensión de sintaxis para JavaScript que permite escribir HTML en código React:

  • No es HTML: Es azúcar sintáctico que se transpila a JavaScript
  • Reglas básicas:
    • Elementos deben cerrarse: <div /> o <div></div>
    • Atributos usan camelCase: className en lugar de class
    • Estilo se pasa como objeto: style={{ color: 'red' }}
  • Expresiones JavaScript: Se incluyen con { }

JSX vs HTML

Diferencias clave entre JSX y HTML tradicional:

Característica HTML JSX
Atributo class class="container" className="container"
Estilos en línea style="color: red;" style={{ color: 'red' }}
Eventos onclick="handleClick()" onClick={handleClick}
Valores booleanos disabled="true" disabled={true}

Mejor Práctica:

Usa el plugin ESLint eslint-plugin-react para mantener consistencia en tu código JSX y detectar errores comunes.

TypeScript con React

Fundamentos
React con TS
Avanzado

Tipado Básico

TypeScript añade tipos estáticos a JavaScript:

// Tipos primitivos
const name: string = 'Carlos';
const age: number = 30;
const isActive: boolean = true;

// Arrays
const numbers: number[] = [1, 2, 3];
const mixed: (string | number)[] = ['a', 1, 'b', 2];

// Objetos
interface User {
    id: number;
    name: string;
    email?: string; // Opcional
}

const user: User = {
    id: 1,
    name: 'María'
};

// Funciones
function greet(name: string): string {
    return `Hola, ${name}!`;
}

// Tipos personalizados
type Status = 'active' | 'inactive' | 'pending';
const userStatus: Status = 'active';

Beneficios del Tipado

TypeScript ofrece varias ventajas para proyectos React:

  • Detección temprana de errores: Encuentra problemas durante el desarrollo
  • Mejor autocompletado: Los editores pueden ofrecer sugerencias más precisas
  • Documentación incorporada: Los tipos sirven como documentación viva
  • Refactorización segura: Cambiar código es menos riesgoso
  • Mejor colaboración: Los tipos hacen las intenciones del código más claras

Ejemplo de Error Detectado:

interface Product {
    id: number;
    name: string;
    price: number;
}

function displayProduct(product: Product) {
    console.log(`${product.name}: $${product.prie}`); 
    // Error: La propiedad 'prie' no existe en 'Product'
}

Componentes Tipados

TypeScript mejora los componentes React con tipado fuerte:

// Props tipados
interface ButtonProps {
    text: string;
    onClick: () => void;
    variant?: 'primary' | 'secondary' | 'danger';
    disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({ 
    text, 
    onClick, 
    variant = 'primary',
    disabled = false
}) => {
    return (
        <button
            className={`btn btn-${variant}`}
            onClick={onClick}
            disabled={disabled}
        >
            {text}
        </button>
    );
};

// Uso del componente
<Button 
    text="Enviar" 
    onClick={() => alert('Enviado!')} 
    variant="secondary"
/>

Hooks con TypeScript

Los hooks de React también se benefician del tipado:

// useState con tipos
const [count, setCount] = useState<number>(0);
const [user, setUser] = useState<User | null>(null);

// useReducer con tipos
type Action = 
    | { type: 'increment' } 
    | { type: 'decrement' }
    | { type: 'reset'; payload: number };

interface State {
    count: number;
}

const reducer = (state: State, action: Action): State => {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        case 'reset':
            return { count: action.payload };
        default:
            return state;
    }
};

const [state, dispatch] = useReducer(reducer, { count: 0 });

Tipos Genéricos

Los genéricos permiten crear componentes flexibles y reutilizables:

// Componente genérico para listas
interface ListProps<T> {
    items: T[];
    renderItem: (item: T) => React.ReactNode;
}

function List<T>({ items, renderItem }: ListProps<T>) {
    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>{renderItem(item)}</li>
            ))}
        </ul>
    );
}

// Uso con diferentes tipos
const users = [{ id: 1, name: 'Ana' }, { id: 2, name: 'Carlos' }];
const products = [{ id: 1, title: 'Laptop' }, { id: 2, title: 'Phone' }];

// Lista de usuarios
<List 
    items={users}
    renderItem={(user) => <span>{user.name}</span>}
/>

// Lista de productos
<List
    items={products}
    renderItem={(product) => <span>{product.title}</span>}
/>

Utilidades de Tipos

TypeScript ofrece tipos utilitarios para patrones comunes:

// Partial: hace todas las propiedades opcionales
type PartialUser = Partial<User>;

// Readonly: hace todas las propiedades de solo lectura
type ReadonlyUser = Readonly<User>;

// Pick: selecciona propiedades específicas
type UserName = Pick<User, 'name'>;

// Omit: excluye propiedades específicas
type UserWithoutId = Omit<User, 'id'>;

// Record: crea un tipo con propiedades de un tipo específico
type Pages = Record<string, { title: string, content: string }>;

// Ejemplo avanzado: Componente con props condicionales
type ModalProps = {
    isOpen: boolean;
    onClose: () => void;
} & (
    | { variant: 'alert'; confirmText: string }
    | { variant: 'confirm'; confirmText: string; cancelText: string }
    | { variant: 'custom'; children: React.ReactNode }
);

Mejor Práctica:

Usa React.FC para componentes funcionales y define interfaces para props complejas. Para props opcionales, usa el operador ?.

Patrones de Componentes

Props y Children
Renderizado Condicional
Estilos

Props Básicos

Los props permiten pasar datos a componentes hijos:

interface CardProps {
    title: string;
    description: string;
    imageUrl?: string;
    rating?: number;
}

const Card: React.FC<CardProps> = ({ 
    title, 
    description, 
    imageUrl,
    rating = 0
}) => {
    return (
        <div className="card">
            {imageUrl && <img src={imageUrl} alt={title} />}
            <h3>{title}</h3>
            <p>{description}</p>
            <div className="stars">
                {'★'.repeat(rating).padEnd(5, '☆')}
            </div>
        </div>
    );
};

// Uso
<Card 
    title="Producto Ejemplo"
    description="Descripción del producto"
    imageUrl="/product.jpg"
    rating={4}
/>

Children y Composición

El prop children permite anidar componentes:

interface LayoutProps {
    sidebar?: React.ReactNode;
    children: React.ReactNode;
}

const Layout: React.FC<LayoutProps> = ({ sidebar, children }) => {
    return (
        <div className="layout">
            {sidebar && <aside className="sidebar">{sidebar}</aside>}
            <main className="content">{children}</main>
        </div>
    );
};

// Uso
<Layout
    sidebar={<NavMenu />}
>
    <h1>Contenido Principal</h1>
    <p>Este es el cuerpo de la página</p>
</Layout>

Renderizado Condicional

React ofrece varias formas de mostrar contenido condicionalmente:

// 1. Operador ternario
function Greeting({ user }) {
    return (
        <div>
            {user ? (
                <h1>Hola, {user.name}!</h1>
            ) : (
                <button>Iniciar sesión</button>
            )}
        </div>
    );
}

// 2. Operador &&
function Notification({ messages }) {
    return (
        <div>
            {messages.length > 0 && (
                <p>Tienes {messages.length} mensajes nuevos</p>
            )}
        </div>
    );
}

// 3. Variables intermedias
function Page({ isLoading, content }) {
    let pageContent;
    if (isLoading) {
        pageContent = <Spinner />;
    } else if (content) {
        pageContent = <Article content={content} />;
    } else {
        pageContent = <EmptyState />;
    }
    
    return <div className="page">{pageContent}</div>;
}

Renderizado de Listas

El método .map() transforma arrays en elementos React:

interface Product {
    id: number;
    name: string;
    price: number;
    inStock: boolean;
}

function ProductList({ products }: { products: Product[] }) {
    return (
        <ul className="product-list">
            {products.map(product => (
                <li key={product.id} className={!product.inStock ? 'out-of-stock' : ''}>
                    <h3>{product.name}</h3>
                    <p>Precio: ${product.price.toFixed(2)}</p>
                    {!product.inStock && <span className="badge">Agotado</span>}
                </li>
            ))}
        </ul>
    );
}

// Uso con TypeScript
const products: Product[] = [
    { id: 1, name: 'Laptop', price: 999.99, inStock: true },
    { id: 2, name: 'Mouse', price: 19.99, inStock: false }
];

<ProductList products={products} />

Enfoques de Estilado

React ofrece múltiples formas de aplicar estilos:

1. CSS Tradicional

/* styles.css */
.button {
    padding: 8px 16px;
    background: #3498db;
    color: white;
}

// Componente
import './styles.css';

const Button = () => (
    <button className="button">Click me</button>
);

2. Módulos CSS

/* Button.module.css */
.primary {
    background: #3498db;
}

// Componente
import styles from './Button.module.css';

const Button = () => (
    <button className={styles.primary}>Click me</button>
);

3. Estilos en Línea

const buttonStyle = {
    padding: '8px 16px',
    backgroundColor: '#3498db',
    color: 'white'
};

const Button = () => (
    <button style={buttonStyle}>Click me</button>
);

Bibliotecas CSS-in-JS

Soluciones modernas para estilado en React:

  • Styled Components: Componentes estilados con template literals
  • Emotion: Similar pero con mejor rendimiento en algunos casos
  • Tailwind CSS: Utilidades CSS con clases predefinidas
// Ejemplo con Styled Components
import styled from 'styled-components';

const StyledButton = styled.button`
    padding: 8px 16px;
    background: ${props => props.primary ? '#3498db' : '#eee'};
    color: ${props => props.primary ? 'white' : '#333'};
`;

const Button = ({ primary, children }) => (
    <StyledButton primary={primary}>
        {children}
    </StyledButton>
);

// Uso
<Button primary>Principal</Button>
<Button>Secundario</Button>

Recomendación:

Para proyectos pequeños, CSS Modules es una excelente opción. Para aplicaciones grandes, considera Styled Components o Emotion.

Resumen Semana 6

Conceptos Clave

  • DOM Virtual y proceso de reconciliación en React
  • Creación de componentes funcionales con JSX/TSX
  • Tipado estático con TypeScript para props y estado
  • Renderizado condicional y de listas
  • Composición de componentes con props y children

Herramientas Dominadas

  • JSX para estructuración de componentes
  • TypeScript para tipado estático
  • Patrones de diseño de componentes reutilizables
  • Múltiples enfoques para estilado en React
  • React DevTools para depuración

Próximos Pasos

  • Profundizar en hooks avanzados (useEffect, useReducer)
  • Aprender gestión de estado global con Context API o Redux
  • Explorar React Router para navegación
  • Practicar integración con APIs usando TypeScript
  • Experimentar con frameworks como Next.js

Reflexión sobre el Aprendizaje

¿Qué aprendí?

Esta semana profundicé en conceptos avanzados de React y TypeScript:

  • DOM Virtual: Comprendí cómo React optimiza las actualizaciones del DOM real mediante el diffing y reconciliación
  • JSX avanzado: Dominé el uso de expresiones JavaScript, renderizado condicional y de listas dentro de JSX
  • TypeScript con React: Aprendí a tipar componentes, props, hooks y estados para mayor seguridad de tipos
  • Patrones de componentes: Implementé componentes reutilizables con props, children y composición
  • Estilado: Experimenté con diferentes enfoques para aplicar CSS en componentes React

¿Cómo aprendí?

Mi proceso de aprendizaje combinó teoría y práctica:

  • Documentación oficial: Estudié a profundidad la documentación de React y TypeScript
  • Proyectos pequeños: Implementé componentes específicos para practicar cada concepto
  • Depuración: Usé React DevTools y el tipado de TypeScript para entender el flujo de datos
  • Comparación: Implementé los mismos componentes con y sin TypeScript para ver las diferencias
  • Retos personales: Me desafíe a crear componentes cada vez más complejos y reutilizables

El momento más revelador fue cuando entendí cómo el sistema de tipos de TypeScript puede prevenir errores incluso antes de ejecutar el código, especialmente al trabajar con props de componentes complejos.

Próximos Pasos

Para continuar mi aprendizaje:

  • Profundizar en hooks avanzados como useEffect, useReducer y useContext
  • Aprender patrones avanzados de gestión de estado (Context API, Redux, Zustand)
  • Explorar React Router para aplicaciones multipágina
  • Practicar testing de componentes con Jest y React Testing Library
  • Experimentar con frameworks como Next.js para renderizado del lado del servidor

También planeo crear un proyecto completo que integre todos estos conceptos, posiblemente un dashboard interactivo con conexión a una API real.