一文读懂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!");
};
相关推荐
熊的猫28 分钟前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
我要洋人死2 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人2 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人2 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR2 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香2 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596933 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai3 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书