一文读懂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,表示属性是只读属性,每一次遍历返回的值为 TP

下面我们先看下 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!");
};
相关推荐
爱勇宝几秒前
《道德经》第5章:生产环境不相信眼泪
前端·后端·架构
掘金者阿豪4 分钟前
LEFT JOIN 凭空消失的背后,是优化器偷偷帮你做了决定
前端·后端
HackTwoHub13 分钟前
AntiDebug Mcp、AI逆向绕过前端,Hook 加密接口,抓取 Vue 路由漏洞,接入 MCP 让 AI 自动化挖掘前端漏洞
前端·vue.js·人工智能·安全·web安全·网络安全·系统安全
Mr_凌宇15 分钟前
AI 应用工程进阶扩展学习笔记 二
前端·面试
Hyyy28 分钟前
Git Worktree 完全讲解
前端·后端·面试
葡萄城技术团队1 小时前
AI 不会淘汰电子表格,反而会让电子表格成为 AI 操作系统
前端·人工智能
BigTopOne1 小时前
Short Put and Short Call
前端
Cache技术分享1 小时前
465. Java 反射 - 获取类的简单名
前端·后端
用户0934077735141 小时前
HarmonyOS WPS Open SDK 实践:最小打开闭环与 enableEdit 分支处理
typescript·harmonyos
BigTopOne1 小时前
什么是行权价,什么是权利金,什么是行权?
前端