泛型类型推断
泛型类型推断的基本概念
TypeScript的泛型类型推断允许编译器根据上下文自动推导出泛型类型参数。当调用泛型函数或使用泛型类时,如果类型参数可以从使用场景中明确推断出来,就不需要显式指定类型参数。这种机制大大减少了冗余的类型注解,使代码更加简洁。
function identity<T>(arg: T): T {
return arg;
}
// 不需要显式指定类型
const result = identity("hello"); // 推断为 string
const num = identity(42); // 推断为 number
函数调用时的类型推断
在调用泛型函数时,TypeScript会根据传入参数的类型自动推断类型参数。这个过程发生在函数调用的瞬间,编译器会分析参数类型并确定最合适的泛型类型。
function mapArray<T, U>(arr: T[], mapper: (item: T) => U): U[] {
return arr.map(mapper);
}
const numbers = [1, 2, 3];
const strings = mapArray(numbers, n => n.toString()); // 推断为 string[]
当函数有多个类型参数时,TypeScript会尝试为每个参数找到最佳匹配。如果某些参数无法推断,可能需要显式指定部分类型参数。
上下文类型推断
上下文类型推断发生在表达式的位置已经确定了预期类型的情况下。这种情况下,TypeScript会利用上下文信息来推断泛型类型参数。
declare function createArray<T>(length: number, value: T): T[];
// 上下文推断
const stringArray: string[] = createArray(3, "x"); // 推断 T 为 string
const numberArray: number[] = createArray(3, 42); // 推断 T 为 number
上下文类型推断在处理回调函数时特别有用:
function fetchData<T>(url: string, parser: (response: string) => T): Promise<T> {
return fetch(url).then(response => parser(response.text()));
}
// 根据返回值类型推断 T 为 User
const userPromise = fetchData("/api/user", json => JSON.parse(json) as User);
泛型约束与类型推断
泛型约束会影响类型推断的行为。当泛型参数有约束时,TypeScript会在满足约束的范围内进行类型推断。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
// 只能传入具有 length 属性的值
loggingIdentity("hello"); // 有效,字符串有 length
loggingIdentity([1, 2, 3]); // 有效,数组有 length
loggingIdentity(42); // 错误,数字没有 length
多重泛型参数推断
当函数有多个泛型参数时,TypeScript会尝试为每个参数找到最合适的类型。有时这些参数之间可能存在依赖关系。
function mergeObjects<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
const merged = mergeObjects({ name: "Alice" }, { age: 30 });
// 推断 T 为 { name: string }, U 为 { age: number }
默认类型参数与推断
泛型可以指定默认类型,当无法推断出类型时会使用默认值。默认类型不会阻止类型推断,只有在确实无法推断时才会使用。
interface PaginatedResponse<T = any> {
data: T[];
total: number;
}
// 不指定类型参数,使用默认 any
const response1: PaginatedResponse = { data: [1, 2, 3], total: 3 };
// 明确指定类型
const response2: PaginatedResponse<string> = { data: ["a", "b"], total: 2 };
// 通过部分参数推断
function createPaginated<T>(data: T[]): PaginatedResponse<T> {
return { data, total: data.length };
}
复杂场景下的类型推断
在一些复杂场景中,类型推断可能会遇到挑战。例如,当使用条件类型或映射类型时,类型推断的行为会更加复杂。
type Unpacked<T> =
T extends (infer U)[] ? U :
T extends Promise<infer U> ? U :
T;
// 推断示例
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<Promise<number>>; // number
type T3 = Unpacked<boolean>; // boolean
在函数中使用条件类型时,类型推断可能会延迟到具体使用时:
function isStringArray<T>(arr: T[]): arr is string[] {
return typeof arr[0] === "string";
}
const array = ["a", "b", "c"];
if (isStringArray(array)) {
// 这里 array 被推断为 string[]
}
类型推断与函数重载
当使用函数重载时,类型推断会考虑所有重载签名,并选择最匹配的一个。这可能导致一些复杂的推断行为。
function processInput(input: string): string;
function processInput(input: number): number;
function processInput(input: string | number): string | number {
if (typeof input === "string") {
return input.toUpperCase();
} else {
return input * 2;
}
}
const strResult = processInput("hello"); // 推断返回 string
const numResult = processInput(10); // 推断返回 number
类型参数推断的限制
虽然TypeScript的类型推断很强大,但在某些情况下仍然需要显式指定类型参数。常见的情况包括:
- 当函数参数不直接使用类型参数时
- 当类型信息不足时
- 当需要更精确的控制时
function createInstance<T>(ctor: new () => T): T {
return new ctor();
}
// 无法从参数推断 T,需要显式指定
const date = createInstance(Date); // 错误
const explicitDate = createInstance<Date>(Date); // 正确
高级推断技巧
通过一些高级技巧,可以更好地控制类型推断。例如,使用类型参数默认值和推断参数:
function setProperty<T, K extends keyof T = keyof T>(
obj: T,
key: K,
value: T[K]
): void {
obj[key] = value;
}
const person = { name: "Alice", age: 30 };
setProperty(person, "name", "Bob"); // 正确
setProperty(person, "age", "30"); // 错误,类型不匹配
另一个技巧是使用推断类型来提取复杂类型的部分:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function getUser(): { name: string; age: number } {
return { name: "Alice", age: 30 };
}
type User = ReturnType<typeof getUser>; // { name: string; age: number }
类型推断与React组件
在React中使用TypeScript时,泛型类型推断对于组件props的类型安全非常重要。函数组件可以自动推断props类型。
interface UserProps {
name: string;
age: number;
}
const UserComponent = ({ name, age }: UserProps) => (
<div>{name} - {age}</div>
);
// 使用时 props 类型会被检查
<UserComponent name="Alice" age={30} />
对于泛型组件,类型推断同样适用:
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function GenericList<T>({ items, renderItem }: ListProps<T>) {
return <div>{items.map(renderItem)}</div>;
}
// 使用时类型会被推断
<GenericList
items={["a", "b", "c"]}
renderItem={(item) => <div>{item}</div>}
/>
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn