Understanding Generics, Extends, and Conditional Types in TypeScript
The article explains TypeScript generics—showing basic, default, and multiple‑parameter functions, generic classes and interfaces, and async request typing—while detailing how the extends keyword enables constraints and powerful conditional types, including distributive behavior, utility types like Pick, Omit, Exclude, and Extract, and the special never type.
Many teams now write projects in TypeScript (TS). This note collects the author’s learning about TS generics, the extends keyword, and conditional types, providing concise explanations and practical examples.
Basic generic function
A generic function keeps the input and output types identical:
function log
(arg: T): T { return arg; }If the function needs to accept several concrete types, a union can be used, but the signature quickly becomes verbose:
function log(arg: string | number): string | number { return arg; }Generics solve this problem by abstracting the type:
function log
(arg: T): T { return arg; }The caller can either specify the type explicitly or let TS infer it:
log
('log'); // explicit log('log'); // inferredDefault generic parameters
function log
(arg: T): T { return arg; }If no type argument is provided, T defaults to string , similar to default parameters in JavaScript.
Multiple generic parameters
function log
(type: T, info: U): [T, U] { return [type, info]; }This allows a function to work with several independent types.
Generic functions for async requests
function request
(url: string): Promise
{ return fetch(url).then(res => res.json()); } interface IUserInfo { name: string; age: number; avatar: string; gender: 'male' | 'female'; city: string; } request
('/getuserinfo').then(res => { console.log(res); });The returned Promise is typed, giving full IntelliSense support.
Generic classes and interfaces
class Stack
{ private data: T[] = []; push(item: T) { return this.data.push(item); } pop(): T | undefined { return this.data.pop(); } } const numStack = new Stack
(); const strStack = new Stack
();Interfaces can also be generic:
interface IUserInfo
{ name: string; age: number; avatar: string; gender: 'male' | 'female'; address: T; } request
>('/getuserinfo').then(res => console.log(res));Utility types: Pick and Omit
type Pick
= { [P in K]: T[P]; }; type Omit
= Pick
>; type TestPick = Pick
; // { name: string } type TestOmit = Omit
; // { age: number; city: string }Conditional types and distributive behavior
type Test = IDog extends IAnimal ? string : number; // string type Test2 = I2 extends I1 ? string : number; // stringWhen the left side of extends is a generic type and the supplied type is a union, the conditional type distributes over each union member:
type P
= T extends 'x' ? string : number; type Result = P<'x' | 'y'>; // string | numberWrapping the generic in a tuple prevents distribution:
type P
= [T] extends ['x'] ? string : number; type Result = P<'x' | 'y'>; // numberSpecial type never
type Test1 = never extends 'x' ? string : number; // string type Test2 = P
; // neverBecause never is an empty union, it triggers distributive conditional types but yields never as the final result.
Utility conditional types: Exclude and Extract
type Exclude
= T extends U ? never : T; type Extract
= T extends U ? T : never; type TestExclude = Exclude<'A' | 'B', 'A'>; // 'B' type TestExtract = Extract<'A' | 'B', 'A'>; // 'A'These are built on the same distributive conditional‑type mechanism.
In summary, extends in TypeScript is used for type inheritance and, together with ternary conditional syntax, enables powerful type transformations such as generic constraints, utility types, and distributive conditional logic.
DaTaobao Tech
Official account of DaTaobao Technology
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.