🚀 TypeScript中的泛型如何与readonly的结合使用?

📅 发布于 2026年1月 | 👤 作者:博主 | 🏷️ 标签:TypeScript, 泛型, readonly, 类型安全, 不可变数据, Web开发, 前端, 面试

欢迎来到我的博客文章!所有文章都是满满的前端干货,文章简明扼要。

核心作用

主要用于在保持类型安全的同时,防止意外修改数据(尤其是函数参数或返回值)。这种组合广泛应用于函数式编程、不可变数据处理和 React 等框架中。

1. 泛型函数参数使用 readonly 数组

function first<T>(arr: readonly T[]): T | undefined {
  return arr[0];
}

const numbers = [1, 2, 3] as const; // 类型:readonly [1, 2, 3]
// 或普通数组:
const list = [10, 20, 30]; // 类型:number[]

first(numbers); // ✅ 允许:readonly [1,2,3] 满足 readonly T[]
first(list); // ✅ 允许:number[] 是 readonly number[] 的子类型(协变)

✅ 优势

使用 readonly T[] 而非 T[],表示函数不会修改传入的数组,更安全。

2. 泛型接口/类型中使用 readonly 属性

interface ReadonlyBox<T> {
  readonly value: T;
}

const box: ReadonlyBox<string> = { value: "hello" };
// box.value = "world"; // ❌ 错误!value 是只读的

3. readonly 与泛型约束(extends)结合

function logLength<T extends readonly any[]>(arr: T): number {
  console.log(arr.length);
  return arr.length;
}

logLength([1, 2, 3]); // ✅ OK
logLength(["a", "b"] as const); // ✅ OK

✅ 作用

通过 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 天然是只读的,配合泛型可实现类型安全的通用组件。

⚠️ 注意事项

完整示例:不可变数据处理

// 示例 1:只读数组操作
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); // [1, 2, 3] 原数组未改变
console.log(newArr); // [1, 2, 3, 4]

// 示例 2:只读对象
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); // { id: 1, name: "Alice" } 原对象未改变
console.log(updated); // { id: 1, name: "Bob" }

实战场景对比

场景 写法 作用
只读数组参数 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 },
};

// config.database.host = "new"; // ❌ 错误!深度只读

总结

💡 核心要点

← 返回首页