Skip to content

React + TypeScript

Aprendamos a utilizar TypeScript en nuestros proyectos de React JS.

¿Qué es TypeScript?

  • TypeScript es un lenguaje de programación desarrollado y mantenido por Microsoft, y es de código abierto.
  • Al ser un superconjunto de JavaScript, cualquier código JavaScript válido es también válido en TypeScript.
  • TypeScript agrega sintaxis adicional a JavaScript para admitir una integración más estrecha con su editor. Detecte errores temprano en su editor.
  • El código TypeScript se convierte a JavaScript, que se ejecuta en cualquier lugar donde se ejecute JavaScript : en un navegador, en Node.js o Deno y en sus aplicaciones.
  • TypeScript entiende JavaScript y utiliza la inferencia de tipos para brindarle excelentes herramientas sin código adicional.

Adopte TypeScript gradualmente

Aplique tipos a su proyecto de JavaScript de forma incremental, cada paso mejora el soporte del editor y mejora su base de código.

ts
interface Account {
  id: number;
  displayName: string;
  version: 1;
}

function welcome(user: Account) {
  console.log(user.id);
}
ts
type Result = "pass" | "fail";

function verify(result: Result) {
  if (result === "pass") {
    console.log("Passed");
  } else {
    console.log("Failed");
  }
}

Instalación

sh
npm create vite@latest .

En el proyecto creado, verás algunos archivos con la extensión .tsx. Estos archivos son componentes de React escritos en TypeScript.

tsconfig.json

Es un archivo de configuración utilizado en proyectos de TypeScript para especificar las opciones y configuraciones del compilador de TypeScript. Estas opciones incluyen cómo se manejan los errores, qué características del lenguaje se habilitan y cómo se genera el código JavaScript resultante.

Cuando TypeScript compila archivos .ts o .tsx, busca un archivo tsconfig.json en el directorio del proyecto. Si no se encuentra un archivo tsconfig.json, TypeScript usa las opciones predeterminadas para la compilación.

Tipos por inferencia

TypeScript conoce el lenguaje JavaScript y generará tipos para usted en muchos casos. Por ejemplo, al crear una variable y asignarla a un valor particular, TypeScript usará el valor como su tipo.

ts
// TypeScript infiere que helloWorld es de tipo string
let helloWorld = "Hello World";

Definición de tipos

Si Typescript no puede inferir el tipo de una variable, puedes definirlo de la siguiente manera:

ts
interface User {
  name: string;
  id: number;
}

const user: User = {
  name: "Hayes",
  id: 0,
};

Si proporciona un objeto que no coincide con la interfaz que ha proporcionado, TypeScript le advertirá:

ts
interface User {
  name: string;
  id: number;
}

//Type '{ username: string; id: number; }' is not assignable to type 'User'.
const user: User = {
  username: "Hayes",
  id: 0,
};

Puede usar interfaces para anotar parámetros y devolver valores a las funciones:

ts
function getAdminUser(): User {
  //...
}

function deleteUser(user: User) {
  //...
}

Tipos en TS

Ya existe un pequeño conjunto de tipos primitivos disponibles en JavaScript: boolean, bigint, null, number, string, symboly, undefined, que puede usar en una interfaz.

TypeScript amplía esta lista con algunos más, como any(permitir cualquier cosa), unknown(asegúrese de que alguien que use este tipo declare cuál es el tipo), never(no es posible que este tipo pueda suceder) y void(una función que devuelve undefinedo no tiene retorno valor).

Verá que hay dos sintaxis para construir tipos: Interfaces y Types . Deberías preferir interface. Úselo type cuando necesite características específicas.

Generics

Una matriz sin genéricos podría contener cualquier cosa. Una matriz con genéricos puede describir los valores que contiene la matriz.

ts
interface Todo {
  text: string;
  complete: boolean;
}

// alternativa: const todos: Todo[] = [...]
const todos: Array<Todo> = [
  {
    text: "Walk the dog",
    complete: false,
  },
  {
    text: "Write app",
    complete: true,
  },
];

Props en React

tsx
import React from "react";

interface Props {
  mensaje: string;
}

// const MiComponente: React.FC<Props> = ({ mensaje }) => {...}
const MiComponente = ({ mensaje }: Props) => {
  return <div>{mensaje}</div>;
};

export default MiComponente;

Práctica v1

tsx
import { useState } from "react";
import AddTodo from "./components/FormAddTodo";

interface Todo {
  text: string;
  complete: boolean;
}

const initialTodos: Todo[] = [
  {
    text: "Walk the dog",
    complete: false,
  },
  {
    text: "Write app",
    complete: true,
  },
];

const App = () => {
  const [todos, setTodos] = useState<Todo[]>(initialTodos);

  const addTodo = (text: string) => {
    const newTodo = { text, complete: false };
    setTodos([...todos, newTodo]);
  };

  const toggleTodo = (index: number) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo, i) =>
        i === index ? { ...todo, complete: !todo.complete } : todo
      )
    );
  };

  const removeTodo = (index: number) => {
    setTodos((prevTodos) => prevTodos.filter((_, i) => i !== index));
  };

  return (
    <div className="container">
      <h1>Todo</h1>
      <AddTodo addTodo={addTodo} />
      <div>
        {todos.map((todo, index) => (
          <article key={index}>
            <fieldset>
              <label htmlFor={`todo-${index}`}>
                <input
                  type="checkbox"
                  id={`todo-${index}`}
                  checked={todo.complete}
                  onChange={() => toggleTodo(index)}
                />
                {todo.text}
              </label>
            </fieldset>
            <button
              className="contrast"
              onClick={() => removeTodo(index)}
            >
              Remove
            </button>
          </article>
        ))}
      </div>
    </div>
  );
};
export default App;

src\components\FormAddTodo.tsx

tsx
import { useState, ChangeEvent, FormEvent } from "react";

interface Props {
  addTodo: (text: string) => void;
}

const FormAddTodo = ({ addTodo }: Props) => {
  const [todo, setTodo] = useState("");

  const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    setTodo(e.target.value);
  };

  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    const trimmedTodo = todo.trim();
    if (!trimmedTodo) return;

    addTodo(trimmedTodo);

    setTodo("");
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="todo">Todo</label>
      <input
        type="text"
        id="todo"
        placeholder="Enter a todo"
        value={todo}
        onChange={handleInputChange}
      />
      <button type="submit">Add</button>
    </form>
  );
};
export default FormAddTodo;

Práctica v2 (components)

src\App.tsx

tsx
import { useState } from "react";
import AddTodo from "./components/FormAddTodo";
import Todos from "./components/Todos";

export interface Todo {
  text: string;
  complete: boolean;
}

const initialTodos: Todo[] = [
  {
    text: "Walk the dog",
    complete: false,
  },
  {
    text: "Write app",
    complete: true,
  },
];

const App = () => {
  const [todos, setTodos] = useState<Todo[]>(initialTodos);

  const addTodo = (text: string) => {
    const newTodo = { text, complete: false };
    setTodos([...todos, newTodo]);
  };

  const toggleTodo = (index: number) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo, i) =>
        i === index ? { ...todo, complete: !todo.complete } : todo
      )
    );
  };

  const removeTodo = (index: number) => {
    setTodos((prevTodos) => prevTodos.filter((_, i) => i !== index));
  };

  return (
    <div className="container">
      <h1>Todo</h1>
      <AddTodo addTodo={addTodo} />
      <div>
        <Todos
          todos={todos}
          removeTodo={removeTodo}
          toggleTodo={toggleTodo}
        />
      </div>
    </div>
  );
};
export default App;

src\components\Todos.tsx

tsx
import { Todo } from "../App";
import TodoItem from "./TodoItem";

interface Props {
  todos: Todo[];
  removeTodo: (index: number) => void;
  toggleTodo: (index: number) => void;
}

const Todos = ({ todos, removeTodo, toggleTodo }: Props) => {
  return (
    <>
      {todos.map((todo, index) => (
        <TodoItem
          key={index}
          todo={todo}
          index={index}
          removeTodo={removeTodo}
          toggleTodo={toggleTodo}
        />
      ))}

      {todos.length === 0 && <p>No todos yet!</p>}
    </>
  );
};
export default Todos;

src\components\TodoItem.tsx

tsx
import { Todo } from "../App";

interface Props {
  todo: Todo;
  index: number;
  toggleTodo: (index: number) => void;
  removeTodo: (index: number) => void;
}

const TodoItem = ({ index, todo, toggleTodo, removeTodo }: Props) => {
  return (
    <article>
      <fieldset>
        <label htmlFor={`todo-${index}`}>
          <input
            type="checkbox"
            id={`todo-${index}`}
            checked={todo.complete}
            onChange={() => toggleTodo(index)}
          />
          {todo.text}
        </label>
      </fieldset>
      <button
        className="contrast"
        onClick={() => removeTodo(index)}
      >
        Remove
      </button>
    </article>
  );
};
export default TodoItem;