需求描述
在我们使用ts日常开发中,经常会遇到这样一种场景,以代码为例:
interface Article {
title: string
content: string
createTime: Date
author: string
readCount: number
}
function createArticle (options: Article) { ... }
假设我要做一个文章的创建,文章的对象有Article类型内的这些属性,然后我用一个函数来创建文章,函数的参数是一个配置信息,配置信息的约束为Article类型。
但是,我们在创建文章的时候,有的信息是可以不传递的,比如readCount可以默认为0;createTime可以默认是当前时间;author可以默认是匿名,那么这个时候,很显然Article类型就不满足我们的需求了。
该怎么解决这种情况呢?我们知道,在ts类型中,给字段加上 ? 可以表示可选,那么也许你会想到,将Article类型改造成下面这样
interface Article {
title: string
content: string
createTime: Date
author: string
readCount: number
}
interface CreateArticleOptios {
title: string
content: string
createTime?: Date
author?: string
readCount?: number
}
function createArticle (options: CreateArticleOptios) { ... }
这样做看上去满足了我们的需求,实际上这样是不可取的,因为Article类型是用来约束文章的,文章中的这些信息肯定是必须的,不能为空,只是说创建的时候参数是可选的。
而且,这样做也会造成重复代码,会给后期维护增加成本,如果哪一天文章的类型字段增加或者减少了,都会导致需要对CreateArticleOptios类型同时进行维护,所以这种办法也是不可取的。
如何解决?
所以我们要如何处理这种需求呢?其实很简单,同样的还是要创建一个新的类型,但是这个新的类型需要是根据Article类型运算出来的类型。先贴代码:
interface Article {
title: string
content: string
createTime: Date
author: string
readCount: number
}
type ArticleOptional = Optional<Article, 'createTime | author | readCount'>;
function createArticle (options: ArticleOptional) { ... }
也就是说,我们希望有这么一种运算(在上述代码中为Optional),传入一个类型,和需要变成可选的字段,它会自动帮我们生成一个对应的新类型。
实现Optional
先上代码
...
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<pick<T, K>>;
type ArticleOptional = Optional<Article, 'createTime | author | readCount'>;
...
我们一步步来解释,首先&是Typescript中的交叉类型,看下面的例子:
interface obj1 {
a: string
b: string
}
interface obj2 {
c: string
d: string
}
type obj3 = obj1 & obj2
const obj: obj3 = {
a: 'a',
b: 'b',
c: 'c',
d: 'd'
}
假如obj2中的c,d都是可选的,那么交叉出来的obj3中的c,d也会是可选的,那么这正是我们想要的。
那么是不是可以理解成,在我们创建的Optional类型中,&前面的部分把 'createTime | author | readCount' 去除掉,只保留没有传递过来的字段,然后&后面的部分,把传进来的字段变成可选,两者交叉之后,就能得到我们想要的结果。
Typescript中的Omit工具方法
Omit是ts中给我们提供的一个工具方法,它的作用很简单,传入两个参数,第一个参数是类型,第二个参数是类型里的字段,它会把传入的字段从类型中去掉。
interface Todo {
title: string
content: string
createdAt: number
date: Date
}
type TodoOmited = Omit<Todo, 'date | content'>
const todo1: TodoOmited = {
title: 'title',
createdAt: 1615544252617
}
Typescript中的Pick工具方法
Pick同样是ts中给我们提供的工具方法,它的作用是从类型中提取出传入的字段参数。
interface Todo {
title: string
content: string
createdAt: number
competed: boolean
}
type TodoPicked = Pick<Todo, 'competed | content'>
const todo1: TodoOmited = {
competed: true,
content: 'content'
}
而Partial的作用,则是把类型中的所有字段都变成可选。
所以最终我们得到的是左边不需要可选的字段和右边需要可选的字段进行交叉,从而得到了我们想要的结果。
总结
在使用ts开发的时候,类似的场景非常之多,我们需要在这一个例子中,学会利用类型体操去解决我们实际开发中遇到的各种需求。
最后~如果本文对你有所帮助,可以点个赞或者请我喝杯奶茶~万分感谢🎉🎉🎉