本文系统梳理TypeScript中常用的内置工具类型,包括Partial、Required、ReadOnly、Pick和Record。通过具体代码示例与源码实现原理分析,帮助开发者理解如何高效进行类型操作,提升代码类型安全性与开发效率。
前言
为了提升开发效率,TypeScript在语言内部提供了丰富的工具类型,例如Partial、Required、ReadOnly等。本文旨在整理并记录这些常用内置工具类型的实际应用场景及其底层源码实现,供开发者查阅参考。
注意:以下为正文内容示例,后续案例可作为参考依据。
一、什么是ts内置工具类型
TypeScript内置了大量类型定义,旨在辅助完成常见的类型转换与操作,这类类型在官方文档中通常被归类为Utility Types(工具类型)。
二、使用示例
1.Partial
该工具类型的作用是将接口或类型定义中的所有必填属性转换为可选属性。
namespace a {
// 示例一:
interface A {
name: string,
age: number,
sex: string
}
// 接口A中的参数通过内置工具类型Partial变成了可选参数
type PartialA = Partial<A>;
let a: PartialA = {
// 此处传参可以任意个数,但只能传已定义的参数
name: '张三',
age: 10
}
// 示例二:
interface Company {
id: number,
name: string
}
interface Person {
id: number,
name: string,
company: Company
}
// 实现DeepPartial,将参数深度转换成可选参数
type DeepPartial<T> = {
[U in keyof T]?: T[U] extends object ? DeepPartial<T[U]> : T[U];
}
type PartialPerson = DeepPartial<Person>;
let p: PartialPerson = {
// 此处的参数已经变为可选参数,可根据自己需要传递
id: 10,
name: '张三',
company: {
name: '美团'
}
}
}
以下是Partial类型的源码实现逻辑。
namespace b {
interface A {
name: string,
age: number,
sex: string
}
type Partial<T> = {
// 原理:
// 1.通过keyof拿到T中的所有key
// 2.迭代所有的key
// 3.统一加上可选参数处理
// 4.返回迭代值的类型
[P in keyof T]?: T[P];
}
// 测试:
type PartialA = Partial<A>;
let a: PartialA = {
name: '张三',
age: 10
}
}
2.Required
该工具类型的作用是将接口或类型定义中的所有可选属性转换为必填属性。
namespace b {
interface Person {
name: string,
age?: number
}
// Required将可选参数变为必填
type RequiredPerson = Required<Person>;
let person: RequiredPerson = {
name: '张三',
age: 10
}
}
以下是Required类型的源码实现逻辑。
namespace c {
interface Person {
name: string,
age?: number
}
type Required<T> = {
// 原理:
// 1.通过keyof拿到T中所有的key
// 2.迭代所有的key
// 3.将可选的参数变为必填的参数
// 4.返回迭代属性及类型
// +?或者?: 代表的是可选,-?: 代表的是不可选
[P in keyof T]-?: T[P];
};
// 测试
type RequiredPerson = Required<Person>;
let person: RequiredPerson = {
name: '李四',
age: 18
}
}
3.ReadOnly
该工具类型的作用是将接口或类型定义中的所有属性标记为只读,禁止后续修改。
namespace d {
interface Person {
name: string,
age?: number
}
type ReadonlyPerson = Readonly<Person>;
let person: ReadonlyPerson = {
name: '张三',
age: 10
}
// 已经变为只读属性,因此此处赋值会报错
// person.name = '李四'; // 无法分配到 "name" ,因为它是只读属性。ts(2540)
// person.age = 20; // 无法分配到 "age" ,因为它是只读属性。ts(2540)
}
以下是Readonly类型的源码实现逻辑。
namespace e {
interface Person {
name: string,
age?: number
}
type Readonly<T> = {
// 原理:
// 1.通过keyof拿到T中所有key
// 2.迭代拿到的所有key
// 3.通过readonly关键字将所有属性变为只读属性
// 4.返回迭代属性及类型
readonly [P in keyof T]: T[P]
}
// 测试
type ReadonlyPerson = Readonly<Person>;
let person: ReadonlyPerson = {
name: '张三',
age: 10
}
// person.name = '李四'; // 无法分配到 "name" ,因为它是只读属性。ts(2540)
// person.age = 20; // 无法分配到 "age" ,因为它是只读属性。ts(2540)
}
4.Pick
该工具类型用于从原有类型中挑选出指定的属性子集,构建一个新的类型。
namespace g {
interface Person {
name: string,
age: number,
sex: string
}
// 参数一是一个对象,参数二是要筛选的属性
type PickPerson = Pick<Person, 'name' | 'sex'>;
let person: PickPerson = {
name: '张三',
// 由于通过Pick只捡取了name和sex属性,因此此时给sex赋值会报错
// 不能将类型“{ name: string; age: number; sex: string; }”分配给类型“PickPerson”。
// 对象文字可以只指定已知属性,并且“age”不在类型“PickPerson”中。ts(2322)
// age: 10,
sex: '男'
}
}
以下是Pick类型的源码实现逻辑。
namespace h {
interface Person {
name: string,
age: number,
sex: string
}
// 原理:
// 1.T表示传入的类型定义。此处指Person接口。
// 2.K表示T的子类型,是一个联合类型。此处指'name' | 'age' | 'sex'的子类型
// 3.通过keyof拿到T中的所有key
// 3.迭代K,拿到所有传递进来的子类型
// 4.返回迭代属性及类型
// 简单理解:就是从一个对象中,提取一部分属性组成新的对象
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
// 测试
type PickPerson = Pick<Person, 'name' | 'age'>
let person: PickPerson = {
name: '张三',
age: 10
}
}
5.Record
Record类型用于构建一种映射关系,将一组键类型映射到指定的值类型上,从而创建新的对象类型。
namespace i {
// 1.此处的K主要用来修饰obj对象的key,为string或者number
// 2.此处的T用来修饰老的类型
// 3.此处的U用来修饰新的类型
function mapObj<K extends string | number, T, U>(obj: Record<K, T>, callable: (x: T) => U) {
// 声明一个变量
let result: Record<K, U> = <Record<K, U>>{};
// 遍历传入的对象
for (const key in obj) {
// 通过callback函数处理result的属性值
result[key] = callable(obj[key]);
}
// 返回result
return result;
}
let obj = { num1: 1, num2: 2 };
let callable = (x: number): string => x * 2 + '';
let newObj = mapObj<string | number, number, string>(obj, callable);
console.log(newObj); // { num1: '2', num2: '4' }
}
以下是Record类型的源码实现逻辑。
namespace j {
type Record<K extends keyof any, T> = {
// 原理:
// 1.通过keyof拿到所有的K,是一个联合类型。string | number | symbol
// 2.迭代K,拿到所有的属性
// 3.返回迭代的属性及类型
// 注意:此处不能写成 type Record<K, T> = {}; any代表所有key的联合类型。
// 否则会报错:不能将类型“K”分配给类型“string | number | symbol”。ts(2322)
[P in K]: T;
};
// 测试
function mapObj<K extends string | number, T, U>(obj: Record<K, T>, callable: (x: T) => U) {
// 声明一个变量
let result: Record<K, U> = <Record<K, U>>{};
// 遍历传入的对象
for (const key in obj) {
// 通过callback函数处理result的属性值
result[key] = callable(obj[key]);
}
// 返回result
return result;
}
let obj = { num1: 1, num2: 2 };
let callable = (x: number): string => x * 2 + '';
let newObj = mapObj<string | number, number, string>(obj, callable);
console.log(newObj); // { num1: '2', num2: '4' }
}
总结
本文仅介绍了上述几种常见的内置工具类型。如需了解更多类型,可访问TypeScript官方文档的Utility Types章节:https://www.typescriptlang.org/docs/handbook/utility-types.html。感谢阅读,我们下次再见。
至此,关于TypeScript内置工具类型的快速入门与运用介绍完毕。如需获取更多相关TypeScript内容,建议查阅本站过往文章或继续浏览后续系列文章,期待大家的支持与关注。