在 TypeScript 中,类型推断有时会过于灵活,导致一些意外的行为:
// 问题示例
function merge<T>(a: T, b: T) {
return { ...a, ...b };
}
// TypeScript 会推断 T 为 number | string
merge(123, "hello"); // 这样是允许的,但可能不是我们想要的
// NoInfer 的实现
type NoInfer<T> = [T][T extends any ? 0 : never];
// 使用 NoInfer 改进
function merge<T>(a: T, b: NoInfer<T>) {
return { ...a, ...b };
}
merge(123, "hello"); // ❌ 错误:类型 'string' 不能赋值给类型 'number'
merge(123, 456); // ✅ 正确
// 确保回调函数的参数类型与原始数据一致
function transform<T>(
data: T[],
callback: (item: NoInfer<T>) => void
) {
data.forEach(callback);
}
const numbers = [1, 2, 3];
transform(numbers, (n: string) => {}); // ❌ 错误
transform(numbers, (n: number) => {}); // ✅ 正确
interface Comparable<T> {
compareTo(other: NoInfer<T>): number;
}
class NumberComparable implements Comparable<number> {
value: number;
constructor(value: number) {
this.value = value;
}
compareTo(other: number): number {
return this.value - other;
}
}
interface CacheConfig<T> {
initialData: T;
validator: (data: NoInfer<T>) => boolean;
serializer?: (data: NoInfer<T>) => string;
}
function createCache<T>(config: CacheConfig<T>) {
// 实现细节
}
// 使用示例
createCache({
initialData: { id: 1, name: "test" },
validator: (data: string) => true // ❌ 错误:类型不匹配
});
type User = {
id: number;
name: string;
};
function updateUser<T extends keyof User>(
field: T,
value: NoInfer<User[T]>
) {
// ...
}
updateUser("id", "123"); // ❌ 错误:期望 number 类型
updateUser("name", "John"); // ✅ 正确
function assertEqual<T>(
actual: T,
expected: NoInfer<T>,
message?: string
) {
if (actual !== expected) {
throw new Error(message || `Expected ${expected} but got ${actual}`);
}
}
interface ValidationRule<T> {
validate: (value: T) => boolean;
message: (value: NoInfer<T>) => string;
}
function clamp<T extends number>(
value: T,
min: NoInfer<T>,
max: NoInfer<T>
): T {
return Math.min(Math.max(value, min), max) as T;
}