TypeScript's real power lives in its type system. This chapter digs into generics for reusable types, narrowing and type guards that follow your control flow, discriminated unions for modeling state safely, utility types for transforming existing types, and the strict mode settings that make all of it trustworthy.
Generics
Generics let a function or type take a type as a parameter, so it can work with many types without forgetting which one it was given. Constrain a type parameter with extends, and combine it with keyof to tie an object's keys to their value types.
function first<T>(items: T[]): T | undefined {
return items[0];
}
const n = first([1, 2, 3]); // number | undefined
const s = first(["a", "b"]); // string | undefined
function longest<T extends { length: number }>(a: T, b: T): T {
return a.length >= b.length ? a : b;
}
longest("hello", "hi"); // string
longest([1, 2], [1, 2, 3]); // number[]
function getProp<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Kim", age: 30 };
const age = getProp(user, "age"); // number
// getProp(user, "email"); // error: not a key of user
interface ApiResponse<T> {
data: T;
status: number;
}
With any you could accept anything, but the return type would be lost. Generics keep both flexibility and safety.
Narrowing and type guards
A value with a union type gets refined to something more specific as it passes through checks. The compiler understands typeof, instanceof, the in operator, equality comparisons, and truthiness checks. To turn your own checking function into a type guard, give it a type predicate of the form value is Type as its return type.
interface Cat { meow(): void }
interface Dog { bark(): void }
function speak(pet: Cat | Dog) {
if ("meow" in pet) pet.meow(); // Cat
else pet.bark(); // Dog
}
function isString(value: unknown): value is string {
return typeof value === "string";
}
function shout(input: unknown) {
if (isString(input)) {
console.log(input.toUpperCase()); // narrowed to string
}
}
function len(text: string | null): number {
if (!text) return 0; // filters out null and ""
return text.length; // string
}
Discriminated unions
A discriminated union is a union whose members share a property, usually called kind or type, holding a different literal value in each member. Switch on that property and every branch knows the exact member it is dealing with. Assigning to never in the default branch turns forgotten cases into compile errors when the union grows.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; size: number }
| { kind: "rect"; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case "circle": return Math.PI * shape.radius ** 2;
case "square": return shape.size ** 2;
case "rect": return shape.width * shape.height;
default: {
const unreachable: never = shape; // errors if a case is missing
return unreachable;
}
}
}
0 Kommentare
Anmelden · Melde dich an, um einen Kommentar zu schreiben.