🚀 TypeScript中的泛型如何与readonly的结合使用?
欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。
核心作用
主要用于在保持类型安全的同时,防止意外修改数据(尤其是函数参数或返回值)。这种组合广泛应用于函数式编程、不可变数据处理和 React 等框架中。
1. 泛型函数参数使用 readonly 数组
function first<T>(arr: readonly T[]): T | undefined {
return arr[0];
}
const numbers = [1, 2, 3] as const;
const list = [10, 20, 30];
first(numbers);
first(list);
✅ 优势
使用 readonly T[] 而非 T[],表示函数不会修改传入的数组,更安全。
2. 泛型接口/类型中使用 readonly 属性
interface ReadonlyBox<T> {
readonly value: T;
}
const box: ReadonlyBox<string> = { value: "hello" };
3. readonly 与泛型约束(extends)结合
function logLength<T extends readonly any[]>(arr: T): number {
console.log(arr.length);
return arr.length;
}
logLength([1, 2, 3]);
logLength(["a", "b"] as const);
✅ 作用
通过 T extends readonly any[] 约束泛型必须是只读数组类型(包括普通数组和 readonly 数组)。
4. React 中的典型应用(Props 为只读)
interface Props<T> {
readonly items: readonly T[];
readonly onSelect: (item: T) => void;
}
function List<T>({ items, onSelect }: Props<T>) {
return (
<ul>
{items.map((item, i) => (
<li key={i} onClick={() => onSelect(item)}>
{String(item)}
</li>
))}
</ul>
);
}
✅ React 特性
React 组件的 props 天然是只读的,配合泛型可实现类型安全的通用组件。
⚠️ 注意事项
T[] 是 readonly T[] 的子类型(可以赋值给 readonly T[]),但反过来不行。 as const 会将字面量推断为最窄的 readonly 类型,常与泛型函数配合使用。 readonly 是编译时检查,运行时仍可通过类型断言绕过(但不推荐)。
完整示例:不可变数据处理
function append<T>(arr: readonly T[], item: T): readonly T[] {
return [...arr, item];
}
const original = [1, 2, 3] as const;
const newArr = append(original, 4);
console.log(original);
console.log(newArr);
interface User {
readonly id: number;
readonly name: string;
}
function updateUser<T extends User>(user: T, updates: Partial<T>): T {
return { ...user, ...updates };
}
const user: User = { id: 1, name: "Alice" };
const updated = updateUser(user, { name: "Bob" });
console.log(user);
console.log(updated);
实战场景对比
| 场景 | 写法 | 作用 |
| 只读数组参数 | readonly T[] | 防止函数内部修改数组 |
| 只读属性 | readonly value: T | 创建不可变对象 |
| 返回只读结构 | Readonly<T> 或 readonly [T, U] | 保证调用者不修改结果 |
| 泛型约束 | T extends readonly any[] | 限制传入类型为只读数组 |
高级用法:深度只读
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object
? DeepReadonly<T[P]>
: T[P];
};
interface Config {
database: {
host: string;
port: number;
};
cache: {
enabled: boolean;
};
}
const config: DeepReadonly<Config> = {
database: { host: "localhost", port: 5432 },
cache: { enabled: true },
};
总结
💡 核心要点
- 类型安全:泛型提供灵活性,readonly 提供不可变性保障
- 函数式编程:配合使用实现纯函数和不可变数据
- React 开发:Props 天然只读,泛型实现通用组件
- 编译时检查:在开发阶段就发现潜在的数据修改问题
- 协变特性:普通数组可以赋值给只读数组参数