够用的 TypeScript(2) | 这八种实用类型的实现你知道吗?

285 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,点击查看活动详情

前言

上篇文章 够用的TypeScript(1)|能用 Javascript 写的东西,终将会用 TypeScript 书写~ 留了一个关于 TS 实用类型的坑~现在来填了~

image.png

大部分效果例子来自官网~


八种实用类型

官方的实用类型有很多~ 但我这里出于篇幅限制,这里只讲前八个比较常用的,以及他的实现~
至于实现的相关语法,会随着用到而进行说明~

Partial 可选

效果

将对象的所有属性值设置为可选——相加了 ?: 一样。

interface Todo {
  title: string;
  description: string;
}
 
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}
 
const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};
 
const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

实现

可能是你没想到的简单

type Partial<T> = {
    [P in keyof T]?: T[P];
};

主要用到TS中的类型映射。将所有的属性都转为?:的,也就是可选的~

类型映射语法

类型映射,大概就是这样~

{ [ P in K ] : T }

这里的in有点像JS的for..in,也就是遍历 K 中所有属性,再用:映射回到合适的属性值

Required 必选

效果

把对象的所有属性都设置为必选

interface Props {
  a?: number;
  b?: string;
}
 
const obj: Props = { a: 5 };
 
const obj2: Required<Props> = { a: 5 };

最后一行代码就会报错~

Property 'b' is missing in type '{ a: number; }' but required in type 'Required'.

实现

type Required<T> = {
    [P in keyof T]-?: T[P];
};

相当于就是通过添加减号-前缀将原来有的?去掉了,让这些属性不是可选的,自然就是必选的~

Readonly 只读

效果

对象或者数组所有属性设置为只读。

interface Todo {
  title: string;
}
 
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
};
 
todo.title = "Hello";

最后一行代码报错:

Cannot assign to 'title' because it is a read-only property.

实现

type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

是的,除了添加减号前缀,你还可以直接添加修饰符~
当然,其实直接添加修饰符也就是默认前面使用了加号+

Record 记录

效果

接收两个参数——keysvalues,使得对象中的keyvalue必须在keysvalues里面。

interface CatInfo {
  age: number;
  breed: string;
}
 
type CatName = "miffy" | "boris" | "mordred";
 
const cats: Record<CatName, CatInfo> = {
  miffy: { age: 10, breed: "Persian" },
  boris: { age: 5, breed: "Maine Coon" },
  mordred: { age: 16, breed: "British Shorthair" },
};

实现

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

keyof

keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
这句K extends keyof any好像有点无厘头,让我们看看这个得到的到底是啥

type KEY =  keyof any //即 string | number | symbol

豁然开朗了,这个不就是说对象的keys类型可以取 stringnumbersymbol类型嘛~
比如这里的例子

type CatName = "miffy" | "boris" | "mordred";

就是keys用了string这个类型,主要还是因为对象的键只能取这些类型,而这些键具体长什么样,就看传入的K
然后再映射到传入的T接口中~

Pick 选择

效果

从接口中选择一组属性值(通过key——属性值)

interface Todo {
  title: string;
  description: string;
  completed: boolean;
}
 
type TodoPreview = Pick<Todo, "title" | "completed">; //从 Todo 接口中选
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
};
 
todo;

实现

type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

通过 第二个参数 keyof T实现~

Omit 过滤

效果

通过key选择来过滤,差不多就是上一个的取反~

interface Todo {
  title: string;
  description: string;
  completed: boolean;
  createdAt: number;
}
 
type TodoPreview = Omit<Todo, "description">;
 
const todo: TodoPreview = {
  title: "Clean room",
  completed: false,
  createdAt: 1615544252770,
};

实现

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

还真是 对 Pick 进行取反~要用到下面要说的 Exclude

简单理解Exclude就是数学集合中找出Type的“差集”,就是将类型A与B对比,返回A中独有的类型

Exclude 排除

效果

顾名思义,include包含的反义词,从一个类型(联合类型)中排除另一个类型。

type T0 = Exclude<"a" | "b" | "c", "a">; //"b"|"c"

实现

前面的 Omit 实现用到这个,那么这个是怎么实现的呢

type Exclude<T, U> = T extends U ? never : T;

?: 条件运算符

这里实际上就是一个?:条件运算符

好像很多语言都有这个东东呢,确实是非常好用~

判断 T 是否来自 U:

  • 啊,真来自 U 啊,不要了,给你个 never
  • 不是?自己人自己人,come on

Extract 提炼

效果

从某个联合类型中,筛选出和另外一个联合类型相交的部分。

type T0 = Extract<"a" | "b" | "c", "a" | "f">; //"a"

实现

type MyExtract<T,U> = T extends U ? T : never ;

和上一个 Exclude很像哦
判断是不是来自 U

  • 是,通过~
  • 不是,never!

学习资源

总结

看完之后相信你对 TS 实用类型的效果、使用、实现都已经有所掌握了~

思维导图总结

image.png

文中措辞、知识点、格式如有疑问或建议,欢迎评论~你对我很重要~
🌊如果有所帮助,欢迎点赞关注,一起进步⛵这对我很重要~