Type parameter constraints
Basic Concepts of Type Parameter Constraints
TypeScript's type parameter constraints allow us to limit the range of generic type parameters. By using the extends
keyword after a generic type parameter, we can specify the conditions that the type parameter must satisfy. This mechanism ensures type safety while maintaining the flexibility of generics.
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
Single Constraint vs. Multiple Constraints
Type parameters can be constrained by a single interface or type, or multiple constraints can be achieved through union types. When multiple conditions need to be satisfied, intersection types can be used.
// Single constraint
function process<T extends string>(value: T): void {
console.log(value.toUpperCase());
}
// Multiple constraints
interface Serializable {
serialize(): string;
}
interface Identifiable {
id: number;
}
function persist<T extends Serializable & Identifiable>(item: T): void {
const serialized = item.serialize();
localStorage.setItem(`item_${item.id}`, serialized);
}
Combining Constraints with Default Types
Type parameter constraints can be combined with default types, which is particularly useful when creating flexible and type-safe APIs.
interface PaginationOptions<T = any> {
pageSize?: number;
currentPage?: number;
filter?: T extends object ? Partial<T> : never;
}
function paginate<T extends object = {}>(options: PaginationOptions<T> = {}) {
// Implementation logic
}
Property Constraints Using keyof
The keyof
operator is often used with type constraints to ensure only existing properties of an object can be accessed.
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const person = { name: "Alice", age: 30 };
getProperty(person, "name"); // Correct
getProperty(person, "gender"); // Error: gender is not a property of person
Constructor Constraints
Using the new
constraint, we can restrict type parameters to be constructor functions, which is particularly useful in factory patterns.
interface Constructor<T> {
new (...args: any[]): T;
}
function createInstance<T>(ctor: Constructor<T>, ...args: any[]): T {
return new ctor(...args);
}
class Person {
constructor(public name: string) {}
}
const alice = createInstance(Person, "Alice");
Recursive Type Constraints
Type constraints can be used to define recursive types, which is useful when working with tree structures or nested data.
interface TreeNode<T> {
value: T;
children?: TreeNode<T>[];
}
function walkTree<T>(node: TreeNode<T>, callback: (value: T) => void) {
callback(node.value);
node.children?.forEach(child => walkTree(child, callback));
}
Constraints in Conditional Types
Constraints can be used in conditional types to create more complex type logic.
type NonNullablePropertyKeys<T> = {
[K in keyof T]: null extends T[K] ? never : K
}[keyof T];
interface User {
name: string;
age: number | null;
email: string | null;
}
type RequiredUserKeys = NonNullablePropertyKeys<User>; // "name"
Constraints with Mapped Types
Combined with mapped types, constraints can be used to transform the properties of object types.
type ReadonlyProps<T, K extends keyof T> = {
readonly [P in K]: T[P];
} & {
[P in Exclude<keyof T, K>]: T[P];
};
interface Config {
apiUrl: string;
timeout: number;
retries: number;
}
type ReadonlyConfig = ReadonlyProps<Config, "apiUrl">;
Advanced Constraints in Generic Functions
Complex constraints can be used to create highly type-safe utility functions.
function mergeObjects<T extends object, U extends object>(
obj1: T,
obj2: U
): T & U {
return { ...obj1, ...obj2 };
}
const merged = mergeObjects({ a: 1 }, { b: 2 }); // { a: 1, b: 2 }
Type Constraints and Function Overloading
Type constraints can be combined with function overloading to provide precise type inference for different input types.
function formatInput<T extends string | number>(input: T): string;
function formatInput(input: boolean): "yes" | "no";
function formatInput(input: any): string {
if (typeof input === "boolean") {
return input ? "yes" : "no";
}
return String(input);
}
Constraints in React Components
In React components, type constraints can ensure the correctness of props.
interface BaseProps {
className?: string;
style?: React.CSSProperties;
}
function withCustomProps<T extends BaseProps>(
Component: React.ComponentType<T>
) {
return (props: T) => <Component {...props} />;
}
Type Constraints and Index Signatures
Constraints can be used to limit the key types of index signatures.
type StringMap<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
interface MixedData {
id: string;
count: number;
name: string;
timestamp: Date;
}
type StringOnly = StringMap<MixedData>; // { id: string; name: string }
Constraints Combined with Promises
In asynchronous programming, constraints can ensure the type of Promise resolution values.
async function fetchData<T extends { id: string }>(url: string): Promise<T[]> {
const response = await fetch(url);
return response.json();
}
interface Product {
id: string;
name: string;
price: number;
}
fetchData<Product>("/api/products"); // Returns Promise<Product[]>
Performance Considerations for Type Constraints
While type constraints provide safety, overly complex constraints may impact compilation performance. For large projects, a balance between type safety and compilation speed is needed.
// Simple constraints compile faster
function simpleConstraint<T extends number | string>(value: T) {}
// Complex constraints may require more compilation time
type ComplexConstraint<T> = T extends infer U
? U extends object
? keyof U extends string
? { [K in keyof U]: U[K] }
: never
: U
: never;
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:类型实例化与延迟求值
下一篇:字符串操作类型