一文读懂TypeScript泛型工具类型Readonly<T>

在 TypeScript 中,我们如果想把某个类型中的所有属性都变为只读属性,在不使用泛型工具类型的情况下,我们可能会这么写:

typescript 复制代码
interface Person {
  readonly id: number;
  readonly name: string;
  readonly age: number;
  readonly gender: string;
}

在上面这段代码中,我们定义了一个名为 Person 的接口,然后给里面的每个属性名前面加上 readonly 关键字,此时 Person 里面的所有属性都变为只读属性。

在我们声明对象,并且指定类型为 Person 时,我们就必须提供 Person 里面的所有属性。

typescript 复制代码
const person: Person = {
  id: 1,
  name: 'Echo',
  age: 26,
  gender: 'Male',
}

以上的方法,可以将某个类型的所有属性变为只读属性,但写起来比较繁琐,代码的可维护性也比较差,而 TS 为我们提供了一个内置的泛型工具类型 Readonly<T>

1. 定义

在 TypeScript 中,泛型工具类型 Readonly<T> 主要用于将指定类型的所有属性设置为只读属性,也就是说,这些属性不能被重新赋值。

2. 源码

Readonly 在 TypeScript 中的源码实现:

typescript 复制代码
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

实现原理:

  • type Readonly<T>:使用关键字 type 定义一个类型别名 Readonly ,它接收泛型 T 作为参数。
  • keyof T:通过 keyof 操作符获取泛型 T 中的所有 key(可以理解成获取对象中的属性名),它返回的是由所有属性名组成的联合类型。
  • in:用于遍历泛型 T 中的每个属性名。
  • T[P]:获取泛型 T 中 P 的类型。(可以理解成 JS 中访问对象属性值的方式)。
  • readonly:将类型 T 的每个属性都设置为只读属性。
  • { readonly [P in keyof T]: T[P] }:这是一个映射类型的语法,通过遍历 keyof T 返回的联合类型,然后使用变量 P 来接收,P 就相当于对象中的 key,然后在 key 前面加上 readonly,表示属性是只读属性,每一次遍历返回的值为 T[P]。

下面我们先看下 Readonly 的基本用法:

ini 复制代码
interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;

上面这段代码中,定义了一个名为 Person 的接口,里面有2个必选属性 name 和 age。接着,我们使用泛型工具类型 Readonly 创建一个新的类型,将 Person 中的所有属性设置为只读的属性,那么 ReadonlyPerson 的类型将等同于下面的这段代码:

typescript 复制代码
type ReadonlyPerson = {
  readonly name: string;
  readonly age: number;
}

3. 使用场景

3.1. 防止意外修改对象的属性

typescript 复制代码
interface Person {
  name: string;
  age: number;
}

// 这里使用 Readonly<Person> 将接口 Person 中所有的属性都设置为只读属性
let readonlyPerson: Readonly<Person> = {
  name: "Echo",
  age: 26,
}

// 可以读取属性
console.log(readonlyPerson.name);

// 但是不能对属性重新赋值
readonlyPerson.name = "Steven"; // 报错:无法为"name"赋值,因为它是只读属性

上面这段代码中,我们定义了一个名为 Person 的接口,里面有2个必选属性 name 和 age。接着,定义了一个对象 readonlyPerson,类型为 Readonly,将 Person 中的所有属性设置为只读的属性,此时,我们可以读取对象中的属性,但是不能对属性重新赋值。

3.2. 作为函数参数类型

在函数参数中使用 Readonly 可以确保函数内部不会修改传入的对象的属性。

typescript 复制代码
interface Person {
  name: string;
  age: number;
}

const getValueByKey: (person: Readonly<Person>) => void = person => {
  console.log(person.name);   // 正确:输出 Echo
  console.log(person.age);    // 正确:输出 26

  // person.name = "James";   // 报错:无法为"name"赋值,因为它是只读属
  // person.age = 36;         // 报错:无法为"age"赋值,因为它是只读属性
}

let person: Person = {
  name: "Echo",
  age: 26,
}
getValueByKey(person)

对于数组,我们可以使用 ReadonlyArray 来创建一个只读的数组。

swift 复制代码
// 使用 ReadonlyArray<number> 来创建一个 number 类型的只读的数组
let readonlyArr: ReadonlyArray<number> = [1, 2, 3];

// readonlyArr.push(4); // 报错:类型"readonly number[]"上不存在属性"push"

4. 注意事项

需要注意的是,如果我们使用 Readonly 将指定类型的所有属性设置为只读属性,不能再对对象中的属性重新赋值。

而且,Readonly<T> 只会将直接属性变为只读,而不会影响嵌套的属性。

typescript 复制代码
interface Person {
  id: number;
  name: string;
  age: number;
  children: {
    name: string;
    gender: string;
    school: string;
    city: string;
  }
}

type ReadonlyPerson = Readonly<Person>

上面这段代码中,ReadonlyPerson的类型等同于下面这段代码:

typescript 复制代码
type ReadonlyPerson = {
  readonly id: number;
  readonly name: string;
  readonly age: number;
  readonly children: {
    name: string;
    gender: string;
    school: string;
    city: string;
  };
}

还有另外一个需要注意的是:在 TypeScript 中,Readonly<T> 工具类型不仅可以作用于属性级别的只读性,而且也作用于包含在对象中的函数或方法。这意味着通过 Readonly<T> 创建的只读对象的属性和方法都不能被修改。

typescript 复制代码
interface Person {
  name: string;
  age: number;
  sayHello: () => void;
}

const person: Readonly<Person> = {
  name: 'Echo',
  age: 26,
  sayHello: () => {
    console.log(`Hello, my name is ${person.name}`);
  },
};

// 可以调用属性和方法
console.log(person.name); // 输出:Echo
person.sayHello(); // 输出:Hello, my name is Echo
    
// 报错:不能为属性和方法重新赋值
person.name = 'Steven'; // 报错:无法为"name"赋值,因为它是只读属性。

person.sayHello = () => { // 报错:无法为"sayHello"赋值,因为它是只读属性。
  console.log("Hello, world!");
};
相关推荐
还有你Y2 小时前
Shell 脚本语法
前端·语法·sh
踩着两条虫3 小时前
如何评价VTJ.PRO?
前端·架构·ai编程
Mh4 小时前
鼠标跟随倾斜动效
前端·css·vue.js
小码哥_常5 小时前
Kotlin类型魔法:Any、Unit、Nothing 深度探秘
前端
Web极客码6 小时前
深入了解WordPress网站访客意图
服务器·前端·wordpress
幺风7 小时前
Claude Code 源码分析 — Tool/MCP/Skill 可扩展工具系统
前端·javascript·ai编程
vjmap7 小时前
唯杰地图CAD图层加高性能特效扩展包发布
前端·gis
ZC跨境爬虫7 小时前
3D 地球卫星轨道可视化平台开发 Day7(AI异步加速+卫星系列精简+AI Agent自动评论)
前端·人工智能·3d·html·json
ID_180079054737 小时前
淘宝 API 上货 / 商品搬家 业务场景实现 + JSON 返回示例
前端·javascript·json
M ? A7 小时前
Vue 动态组件在 React 中,VuReact 会如何实现?
前端·javascript·vue.js·经验分享·react.js·面试·vureact