Mapping Distributive Types in TypeScript
Last Updated :
04 Oct, 2024
The TypeScript type system is considered to be quite expressive and, like all other languages, contains an interesting feature known as distributive types. The concept of distributive types allows one to perform operations of distributing conditional types over union types, which is extremely useful in many cases of advanced type manipulations when used with mapped types.
Distributive types offer a way to create more complex and better-adapting structures of types. This article will cover distributive types in TypeScript, their relations to union types, and how to utilize the mapping of distributive types.
Below are the following topics we are going to cover:
What Are Distributive Types?
In TypeScript, distributive types are those that happen when conditional types are invoked on union types, a conditional type is said to be distributed into a union of types, put differently, a conditional type offered for a union type applies to each union type element by distributing the condition across the members of the union and generating a fresh type.
Conditional types are distributive in this case it can be visualized as:
type Distributive<T> = T extends U ? X : Y;
If T is a union, then TS will apply this rule to every member of T, which is particularly beneficial for such actions as union-type filtering, transforming, or mapping.
Why Use Distributive Types?
Distributive types are interesting because they support type transformations relevant to each element of a union, these transformations are useful in situations such as:
- Filtering members of a union: Extracting members of a union or cleansing it by removing members under some parameters.
- Mapping unions to other types: Transforming each one of the union members to some particular type.
- Performing complex type-level computations: Such as making conditional or recursive types that have their conditions based on the union member.
Understanding Mapping and Distributive Types
Mapping distributive types is a powerful combination of TypeScript’s conditional types and mapped types, Mapped types let you change the properties of an existing type to form a new type, and distributive types enable transforming the members of a union, the combination of these two concepts helps implement quite complex and safe constructs.
Examples of Mapping Distributive Types
Example 1: Excluding Members from a Union
Distributive types can narrow down a union by excluding specific members using conditional types. For example, ExcludeFromUnion<T, U> removes elements of type U from the union by setting them to never.
JavaScript
type ExcludeFromUnion<T, U> = T extends U ? never : T;
type Union = 'a' | 'b' | 'c';
type Excluded = ExcludeFromUnion<Union, 'a'>;
let result: Excluded;
result = 'b';
result = 'c';
// result = 'a';
// Error: Type '"a"' is not assignable to type 'Excluded'.
console.log(result);
Output:
c
Example 2: Extracting Members from a Union
Similarly you can use distributive types to extract certain members from a union type, in this case ExtractFromUnion type extracts only members of the union that match U.
JavaScript
type ExtractFromUnion<T, U> = T extends U ? T : never;
type Union = 'a' | 'b' | 'c';
type Extracted = ExtractFromUnion<Union, 'a' | 'b'>;
let extractedResult: Extracted;
extractedResult = 'a';
extractedResult = 'b';
// extractedResult = 'c';
// Error: Type '"c"' is not assignable to type 'Extracted'.
console.log(extractedResult);
Output:
b
Example 3: Mapping Union Types to New Types
You can use distributive conditional types to map each member of a union to a new structure. For example, WrapInObject<Union> wraps each member of Union in an object with a value property.
JavaScript
type WrapInObject<T> = { value: T };
type Union = 'a' | 'b' | 'c';
type WrappedUnion = WrapInObject<Union>;
const wrappedA: WrappedUnion = { value: 'a' };
const wrappedB: WrappedUnion = { value: 'b' };
console.log(wrappedA);
console.log(wrappedB);
Output:
{ value: 'a' }
{ value: 'b' }
Example 4: Deep Partial with Mapping Distributive Types
The DeepPartial<T> type recursively applies the Partial utility type to all properties of an object. It uses a distributive conditional type to check if each property is an object, applying itself to mark all properties, including nested ones, as optional.
JavaScript
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface Example {
a: number;
b: string;
c: {
d: boolean;
e: {
f: string;
};
};
}
type PartialExample = DeepPartial<Example>;
const partial: PartialExample = {
a: 42,
c: {
e: {
f: 'hello',
},
},
};
console.log(partial);
Output:
{ a: 42, c: { e: { f: 'hello' } } }
Limitations
- Type Inference Limitations: In more complicated situations TypeScript may tend to have problems in type inference this means that it may lead to unpredictable behavior or even errors.
- Performance: Distributive types can produce some degradation in compile time, especially when used on very complex and deeply nested types.
- Recursive Depth: Recursive types (for example DeepPartial) can only include a certain amount of recursive levels, the errors occur for type recursion if this level is exceeded.
Best Practices
- Use Utility Types: When it comes to these utility types in TypeScript, you usually don’t have to use so when creating types do not even think of it.
- Avoid Overuse: Distributive Types are strong and powerful constructs but they also tend to complicate the understanding and maintenance of code, such type should be used whenever necessary.
- Test with Multiple Scenarios: In case of different custom distributive types, input values at the time will be needed so that the designed types will behave as expected.
Conclusion
The ability to map distributive types in TypeScript is a potent feature that enables developers to modify and change a developer’s types during compilation, for Union Types and complex types, it means there are more type systems at work that allow for even greater expressiveness in the type system, many conventional programming languages have a looser coupling between data structures and associated algorithms, thus, mastery of mappable distributive conditional types is an important process in the evolution of advanced type-level programming that makes your code more robust yet flexible and maintainable.
Similar Reads
Data types in TypeScript
In TypeScript, a data type defines the kind of values a variable can hold, ensuring type safety and enhancing code clarity. Primitive Types: Basic types like number, string, boolean, null, undefined, and symbol.Object Types: Complex structures including arrays, classes, interfaces, and functions.Pri
3 min read
Type Manipulation in TypeScript
TypeScript offers strong tools for types manipulation and transformation, these tools allow programmers to create new types by composing, intersecting, unionizing, mapping and conditioning existing ones, in this article we will investigate some of the advanced type-manipulation features in TypeScrip
3 min read
TypeScript Conditional Types
In TypeScript, conditional types enable developers to create types that depend on a condition, allowing for more dynamic and flexible type definitions. They follow the syntax T extends U ? X : Y, meaning if type T is assignable to type U, the type resolves to X; otherwise, it resolves to Y.Condition
4 min read
Explain Type assertions in TypeScript
In TypeScript, type assertions allow developers to override the compiler's inferred type, informing it of the specific type of a value. Type assertions are purely compile-time constructs and do not alter the runtime behavior of the code. They are particularly useful when interfacing with APIs or thi
4 min read
TypeScript instanceof narrowing Type
TypeScript instanceof operator is used for type narrowing, allowing us to check whether an object is an instance of a specific class or constructor function. When we use instanceof in a conditional statement, TypeScript narrows the type of a variable based on the result of the check. Syntax:if (obje
3 min read
TypeScript Mapped Types
Mapped types in TypeScript allow you to create new types by transforming the properties of existing types. They enable modifications like making properties optional, read-only, or altering their types.Mapped types help reduce code duplication and enhance type safety by automating type transformation
3 min read
TypeScript Specifying Type Arguments
TypeScript is a powerful statically typed superset of JavaScript that allows you to define and enforce types in your code. One of the key features of TypeScript is the ability to specify type arguments when working with generic functions and classes. This article will provide a comprehensive guide o
3 min read
TypeScript Literal Types
TypeScript's literal types allow developers to specify exact values for variables, function parameters, or properties, enhancing type safety by ensuring variables can only hold predefined values. Allow variables to have specific, exact values.Enhance code reliability by restricting permissible value
3 min read
TypeScript Equality Narrowing Type
In this article, we are going to learn about Equality narrowing Type in Typescript. TypeScript is a popular programming language used for building scalable and robust applications. In TypeScript, "equality narrowing" refers to the process of narrowing the type of a variable based on equality checks
4 min read
TypeScript Construct Signatures
TypeScript Construct Signatures define the shape of a constructor function, specifying the parameters it expects and the type of object it constructs. They use the new keyword in a type declaration to ensure the correct instantiation of classes or objects. Syntaxtype Constructor = new (param1: Type1
3 min read