TypeScript's Type Machinery: How keyof, Exclude, and Pick Compose Into Omit
The previous article introduced commonly used utility types in TypeScript:
- Pick: select fields
- Omit: exclude fields
- Partial: make fields optional
- Required: make fields required
The problem these utility types solve is:
How to quickly generate new types based on existing types.
But behind them lies an even more important idea:
TypeScript types themselves can be computed and combined.
This article continues learning several more fundamental type tools in TypeScript:
keyofExcludeRecordReturnType
And understand why Omit can be implemented through the combination of other types.
1. keyof: Get all field names of an object type
Suppose there is a User type:
interface User {
id: number;
name: string;
age: number;
email: string;
}
This type contains:
User
id
name
age
email
If you want to get all field names of User:
type UserKeys = keyof User;
You get:
type UserKeys = "id" | "name" | "age" | "email";
That is to say:
keyof User
will convert the object type into:
A union type composed of all keys.
It can be understood as:
User
{
id
name
age
email
}
|
↓
"id" | "name" | "age" | "email"
2. Exclude: Remove members from a union type
Exclude is used to handle union types.
For example:
type All = "id" | "name" | "age" | "email";
Now if you don't want email:
type AfterExclude = Exclude<All, "email">;
Result:
"id" | "name" | "age"
It can be understood as:
Original union type:
id
name
age
email
Delete:
email
Remaining:
id
name
age
Note:
Exclude handles:
Type sets
not objects.
For example:
type Keys = "a" | "b" | "c";
type Result = Exclude<Keys, "b">;
Result:
"a" | "c"
3. The implementation principle of Omit
Previously learned:
type UserSafe = Omit<User, "email">;
Meaning:
Delete email from User.
Result:
{
id:number;
name:string;
age:number;
}
So how does TypeScript implement Omit?
In fact:
Omit<T, K>
is equivalent to:
Pick<T, Exclude<keyof T, K>>
It looks complex, but it's very simple when broken down.
Step 1: keyof gets all fields
keyof User
Gets:
"id" | "name" | "age" | "email"
Step 2: Exclude removes unwanted fields
Exclude<
"id" | "name" | "age" | "email",
"email"
>
Gets:
"id" | "name" | "age"
Step 3: Pick reselects fields
Pick<User, "id" | "name" | "age">
Gets:
{
id:number;
name:string;
age:number;
}
Therefore:
Omit<User,"email">
Internal logic:
User
↓ keyof
"id" | "name" | "age" | "email"
↓ Exclude deletes email
"id" | "name" | "age"
↓ Pick
New User type
4. Record: Create key-value mapping types
The previous utility types are all:
Modifications based on existing types.
Whereas Record is:
Create a new object type based on rules.
Syntax:
Record<key type, value type>
For example:
type Dict = Record<string, number>;
Means:
Create an object:
- key must be string
- value must be number
So:
const obj: Dict = {
a:1,
b:2
}
Correct.
But:
const obj: Dict = {
a:"hello"
}
Incorrect.
Because value should be number.
Practical application: Error code mapping
HTTP status codes:
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 500 Internal Server Error
Can define:
type ErrorMsgMap = Record<number, string>;
Then:
const errorMessage: ErrorMsgMap = {
400:"Bad request parameters",
401:"Please log in again",
403:"Insufficient permissions",
404:"Resource not found",
500:"Internal server error"
};
Get error message:
function getErrMsg(code:number):string {
return errorMessage[code] ?? "Unknown error";
}
5. ReturnType: Get function return value type
TypeScript can automatically infer function return values:
function fn(){
return {
x:1,
y:2
};
}
TypeScript knows:
{
x:number;
y:number;
}
If you want to directly get this return type:
type FnReturn = ReturnType<typeof fn>;
Break it down:
typeof fn
Get function type:
() => {
x:number;
y:number;
}
ReturnType
Get return value:
{
x:number;
y:number;
}
6. The relationship between these utility types
They do not exist independently.
Many complex types are produced through composition:
Object type User
|
|
keyof
↓
Field union type
|
|
Exclude
↓
Delete fields
|
|
Pick
↓
New object type
7. Summary of common utility types
| Utility Type | Purpose | Example |
|---|---|---|
| keyof | Get all keys of an object | keyof User |
| Pick | Select fields | Pick<User,'id'> |
| Omit | Delete fields | Omit<User,'email'> |
| Exclude | Remove members from a union type | Exclude<A,B> |
| Partial | Make all fields optional | Partial<User> |
| Required | Make all fields required | Required<User> |
| Record | Create key-value mapping | Record<string,number> |
| ReturnType | Get function return type | ReturnType<typeof fn> |
Summary
The core of TypeScript advanced types is not memorizing every utility type, but understanding:
Types can also be computed.
Through:
keyofto get fieldsExcludeto handle union typesPickto recombine object typesRecordto create mapping structuresReturnTypeto extract function types
We can avoid redefining interfaces in large projects, allowing types to automatically synchronize with business changes.
This is also the reason why utility types are heavily used in React + TypeScript projects:
One core type, through type composition, generates the data structures needed for different pages, interfaces, and forms.