Tipos unión
Los tipos unión permiten que un valor sea de varios tipos posibles:
// Tipo unión básico
let id: string | number;
id = "abc123"; // OK
id = 123; // OK
// id = true; // Error!
// Unión en parámetros de función
function printId(id: string | number): void {
console.log("ID:", id);
}
// Unión con arrays
let mixedArray: (string | number)[] = [1, "two", 3, "four"];
// Unión con tipos literales
type Direction = "north" | "south" | "east" | "west";
let heading: Direction = "north";
// Unión con null/undefined
type MaybeString = string | null | undefined;
function processName(name: string | null): string {
if (name === null) {
return "Anonymous";
}
return name.toUpperCase();
}
Narrowing de tipos
Reduce los tipos unión con type guards:
// Narrowing con typeof
function padLeft(value: string, padding: string | number): string {
if (typeof padding === "number") {
// padding es number aquí
return " ".repeat(padding) + value;
}
// padding es string aquí
return padding + value;
}
// Narrowing por truthiness
function processValue(value: string | null | undefined): string {
if (value) {
// value es string aquí (truthy)
return value.toUpperCase();
}
return "default";
}
// Narrowing por igualdad
function compare(a: string | number, b: string | boolean): void {
if (a === b) {
// a y b son ambos string aquí
console.log(a.toUpperCase());
}
}
// Narrowing con operador in
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird): void {
if ("swim" in animal) {
animal.swim(); // animal es Fish
} else {
animal.fly(); // animal es Bird
}
}
// Narrowing con instanceof
function logDate(date: Date | string): void {
if (date instanceof Date) {
console.log(date.toISOString()); // date es Date
} else {
console.log(date); // date es string
}
}
Uniones discriminadas
Usa una propiedad común para discriminar entre los miembros de la unión:
// Unión discriminada con propiedad 'kind'
interface Circle {
kind: "circle";
radius: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Triangle {
kind: "triangle";
base: number;
height: number;
}
type Shape = Circle | Rectangle | Triangle;
function getArea(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
}
}
// Verificación de exhaustividad
function assertNever(x: never): never {
throw new Error("Unexpected value: " + x);
}
function getAreaExhaustive(shape: Shape): number {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "rectangle":
return shape.width * shape.height;
case "triangle":
return (shape.base * shape.height) / 2;
default:
return assertNever(shape); // Error si falta un caso
}
}
Tipos de intersección
Los tipos de intersección combinan múltiples tipos en uno:
// Intersección básica
type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: number;
department: string;
};
type Staff = Person & Employee;
const staff: Staff = {
name: "Alice",
age: 30,
employeeId: 12345,
department: "Engineering"
};
// Intersección con interfaces
interface Timestamps {
createdAt: Date;
updatedAt: Date;
}
interface Identifiable {
id: string;
}
type Entity<T> = T & Timestamps & Identifiable;
type UserEntity = Entity<{ name: string; email: string }>;
const user: UserEntity = {
id: "user-1",
name: "Bob",
email: "bob@example.com",
createdAt: new Date(),
updatedAt: new Date()
};
Type guards definidos por el usuario
// Función con predicado de tipo
interface Cat {
meow(): void;
purr(): void;
}
interface Dog {
bark(): void;
wagTail(): void;
}
// Type guard con palabra clave 'is'
function isCat(animal: Cat | Dog): animal is Cat {
return "meow" in animal;
}
function interact(animal: Cat | Dog): void {
if (isCat(animal)) {
animal.meow(); // animal es Cat
animal.purr();
} else {
animal.bark(); // animal es Dog
animal.wagTail();
}
}
// Type guard para respuesta de API
interface SuccessResponse {
success: true;
data: unknown;
}
interface ErrorResponse {
success: false;
error: string;
}
type ApiResponse = SuccessResponse | ErrorResponse;
function isSuccess(response: ApiResponse): response is SuccessResponse {
return response.success === true;
}
function handleResponse(response: ApiResponse): void {
if (isSuccess(response)) {
console.log("Data:", response.data);
} else {
console.error("Error:", response.error);
}
}
Ejemplos prácticos
// Tipos de acciones en Redux
type LoadUsersAction = {
type: "LOAD_USERS";
};
type AddUserAction = {
type: "ADD_USER";
payload: { name: string; email: string };
};
type RemoveUserAction = {
type: "REMOVE_USER";
payload: { id: number };
};
type UserAction = LoadUsersAction | AddUserAction | RemoveUserAction;
function userReducer(state: User[], action: UserAction): User[] {
switch (action.type) {
case "LOAD_USERS":
return state;
case "ADD_USER":
return [...state, { id: Date.now(), ...action.payload }];
case "REMOVE_USER":
return state.filter(u => u.id !== action.payload.id);
}
}
// Props de componente React con variantes
type ButtonProps = {
label: string;
onClick: () => void;
} & (
| { variant: "primary"; icon?: never }
| { variant: "icon"; icon: string }
);
// Uso:
//
Puntos clave
- • Los tipos unión (
|) permiten uno de varios tipos - • Los tipos de intersección (
&) combinan tipos - • Usa type guards para reducir uniones
- • Las uniones discriminadas usan una propiedad común
- • Los type guards personalizados usan la palabra clave
is - • La verificación de exhaustividad detecta casos faltantes