Pelajaran 7 dari 1012 menit
Generics
Write a function or type once and use it with many types, without giving up type safety. Generics are type parameters.
Pelajaran ini hanya tersedia dalam bahasa Inggris. Antarmuka halaman tetap dalam bahasa Anda.
Dalam pelajaran ini
- Explain what a type parameter is
- Write generic functions and types
- Constrain generics with extends and keyof
The problem generics solve#
Imagine a function that returns the first item of an array. Without generics you either write one per type or give up type information.
function firstAny(items: unknown[]): unknown { return items[0];}const value = firstAny(["a", "b"]);1value is unknown: TypeScript forgot that the array held strings, so you cannot call string methods without checking first.
Type parameters#
A generic function declares a type parameter in angle brackets. TypeScript fills it in from the arguments.
function first<T>(items: T[]): T | undefined {1 return items[0];}const word = first(["a", "b"]);2const num = first([10, 20]);3console.log(word?.toUpperCase(), num?.toFixed(2));T is a placeholder for "whatever type the array holds". The return type says it may be undefined for an empty array.
Here T is inferred as string, so word is string | undefined.
Here T is number. Same function, still fully typed.
Generic types#
Types can take parameters too. A result type that carries either data or an error is a classic example, and you will use it in the next lesson.
type Result<T> = { ok: true; value: T } | { ok: false; error: string };function parseAge(input: string): Result<number> { const age = Number(input); if (!Number.isInteger(age) || age < 0) return { ok: false, error: `"${input}" is not an age` }; return { ok: true, value: age };}const result = parseAge("29");if (result.ok) console.log(result.value + 1); // 30else console.log(result.error);Arrays are generic as well: string[] is shorthand for Array<string>, and Promise<User> is a promise that resolves to a User.
Constraints#
Sometimes a type parameter must have certain features. extends sets a minimum requirement.
function longest<T extends { length: number }>(a: T, b: T): T {1 return a.length >= b.length ? a : b;}console.log(longest("kopi", "teh")); // "kopi"2console.log(longest([1, 2], [1, 2, 3])); // [1, 2, 3]Any T is allowed as long as it has a numeric length: strings and arrays both qualify.
Passing a number would be an error, because numbers have no length.
keyof: safe property access#
keyof T is the union of a type's property names. Combined with a constraint, it lets you write a property getter that cannot ask for a field that does not exist.
function pluck<T, K extends keyof T>(items: T[], key: K): T[K][] {1 return items.map((item) => item[key]);}const people = [ { name: "Rina", age: 31 }, { name: "Dewa", age: 27 },];const names = pluck(people, "name"); // string[]const ages = pluck(people, "age"); // number[]console.log(names, ages);K must be one of T's keys, and T[K] is the type of that property. pluck(people, "email") would not compile.
When not to use generics
If a type parameter appears only once, it adds nothing: function log<T>(value: T): void is just function log(value: unknown): void. Use a generic when it connects types, such as an input to an output. Over-generic code is harder to read than a little duplication.
LatihanA typed last()
Write last<T>(items: T[]): T | undefined that returns the final element. Then call it with an array of booleans and check the inferred type.
Tampilkan solusi
function last<T>(items: T[]): T | undefined { return items[items.length - 1];}const flag = last([true, false, true]); // boolean | undefinedconsole.log(flag);Recap#
- Type parameters let one function or type work with many types while staying precise.
- TypeScript usually infers them from arguments.
- Constrain with
extends; usekeyofandT[K]for safe property access.