More about Type Manipulation
typescript type manipulation
Type Manipulation in TypeScript
Type manipulation in TypeScript refers to the ability to transform or infer types based on certain conditions, allowing you to create more flexible and reusable code. TypeScript provides powerful tools for manipulating types, such as infer
, conditional types, and utility types, among others. Here’s an overview of these key concepts:
1. Conditional Types
Conditional types allow you to select one type over another based on a condition. This is similar to a ternary operator (condition ? true : false
) but for types.
Basic Syntax:
type ConditionalType<T> = T extends SomeCondition ? TrueType : FalseType;
In this example, T
is checked against SomeCondition
. If it matches, the result is TrueType
; otherwise, it will be FalseType
.
2. infer
Keyword for Type Inference
The infer
keyword allows you to extract a type from a more complex type within conditional types. It’s especially useful for situations where you want to infer the type of a function’s return value, arguments, or other components without having to explicitly define it.
Example of infer
with ReturnType
:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
Here, ReturnType<T>
is a conditional type that:
- Checks if
T
is a function (denoted by(...args: any[]) =>
). - If
T
is a function, it infers the return type of that function and assigns it toR
. - If
T
is not a function, it returnsnever
(meaning an invalid type).
Usage Example:
type MyFunction = (x: number, y: number) => string;
type Result = ReturnType<MyFunction>; // Result will be `string`
Here, the type Result
will be string
because the function MyFunction
returns a string
. The ReturnType
utility extracts that return type.
3. Common Use Cases for Type Manipulation
Extracting Function Arguments
You can use the infer
keyword to extract the types of function parameters as well.
type ParameterType<T> = T extends (arg: infer U) => any ? U : never;
type MyFunction = (x: number) => string;
type Param = ParameterType<MyFunction>; // Param will be `number`
In this example, ParameterType
extracts the type of the parameter passed to the function. If MyFunction
takes a number
, then Param
will be number
.
Discriminated Unions with infer
You can use infer
to narrow types in conditional types when dealing with discriminated unions.
type Shape =
| { kind: 'circle', radius: number }
| { kind: 'square', sideLength: number };
type GetShapeType<T> = T extends { kind: 'circle' } ? 'Circle' :
T extends { kind: 'square' } ? 'Square' :
never;
type CircleType = GetShapeType<{ kind: 'circle', radius: 10 }>; // 'Circle'
type SquareType = GetShapeType<{ kind: 'square', sideLength: 20 }>; // 'Square'
Here, we use a conditional type to map over a discriminated union to extract the types based on the kind
property.
4. Using Utility Types for Type Transformation
TypeScript also offers a range of utility types that make it easier to manipulate types in common scenarios. These types include Partial
, Required
, Readonly
, Record
, etc.
Example: Pick
, Omit
, and Exclude
interface Person {
name: string;
age: number;
address: string;
}
// Pick specific keys from the interface
type NameAndAge = Pick<Person, 'name' | 'age'>; // { name: string; age: number }
// Omit specific keys from the interface
type WithoutAddress = Omit<Person, 'address'>; // { name: string; age: number }
// Exclude specific types from a union
type WithoutString = Exclude<string | number | boolean, string>; // number | boolean
Pick<T, K>
: Picks a subset of properties from a typeT
.Omit<T, K>
: Omit specific properties from a typeT
.Exclude<T, U>
: Excludes types from a union type.
These utility types are often used for type manipulation, allowing you to create new types based on existing ones.
5. Key Takeaways of Type Manipulation
Concept | Explanation | Example |
---|---|---|
Conditional Types | Types that select one type over another based on a condition. | type IsString<T> = T extends string ? true : false; |
infer Keyword |
Extracts a type within a conditional type, useful for inferring function return types or extracting types from more complex types. | type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never; |
Function Arguments | Extracts the types of function arguments. | type Param<T> = T extends (arg: infer U) => any ? U : never; |
Discriminated Unions | Conditional types combined with discriminated unions to narrow down types. | type ShapeType<T> = T extends { kind: 'circle' } ? 'Circle' : T extends { kind: 'square' } ? 'Square' : never; |
Utility Types | Built-in utility types like Pick , Omit , Partial , Required , and Exclude allow transformation of types in useful ways. |
type NameAndAge = Pick<Person, 'name' | 'age'>; |
6. Conclusion
Type manipulation in TypeScript allows you to create flexible, reusable, and strongly typed code by leveraging the power of conditional types, infer
, and utility types. These tools enable complex type transformations that are both expressive and maintainable, making TypeScript a robust language for building large and scalable applications.