我想回顾一下这篇文字稿,并加以阐述。而这个系列被称为Tidy TypeScript,所以期待更多的意见性立场。
泛型编程#
TypeScript的泛型可以说是该语言中最强大的功能之一。它们为TypeScript自己的元编程语言打开了一扇门,它允许非常灵活和动态的类型生成。正如Anders Hejlsberg在其2020年TSConf主题演讲中所说,它真的很接近于成为自己的函数式编程语言。
特别是在最近的TypeScript版本中,随着字符串字面类型和递归条件类型的到来,我们可以制作出能做出惊人之举的类型。这个小类型解析了Express风格的路由信息,并检索了一个带有所有参数的对象。
type ParseRouteParameters<T> =
T extends `${string}/:${infer U}/${infer R}` ?
{ [P in U | keyof ParseRouteParameters<`/${R}`>]: string } :
T extends `${string}/:${infer U}` ?
{ [P in U]: string } : {}
type X = ParseRouteParameters<"/api/:what/:is/notyou/:happening">
// type X = {
// what: string,
// is: string,
// happening: string,
// }
强大的功能!(Dan在他的博客上展示了这个类型的一个更复杂的版本,请查看)。
当我们定义一个通用类型时,我们也定义了通用类型的参数。这就是角括号之间的东西,我们有时顺便称之为泛型。
它们可以是某种类型(或者更正确的说法:是某种子类型)。
type Foo<T extends string> = ...
它们可以有默认值。
type Foo<T extends string = "hello"> = ...
当使用默认值时,顺序是很重要的。与普通的JavaScript函数有很多相似之处!那么,既然我们几乎是在谈论函数,为什么我们要为通用类型参数使用单字母名称呢?
命名通用类型参数#
大多数通用类型参数以字母T 开始。后续的参数沿着字母表走(U,V,W),或者是缩写,如K 为key 。
和几乎所有的编程概念一样,泛型的概念已经存在了相当长的时间。通用类型的一些主要实现可以在七十年代的编程语言中看到,比如Ada和ML。
我不知道命名类型参数T ,是否从那时就开始了,还是因为C++中类似的--尽管更强大的--模板概念的流行,导致我们普遍这样称呼它们。关键是。我们这样做已经有很长一段时间了。我们已经习惯于此。
然而,这可能导致高度不可读的类型。如果我看一下Pick<T, U> ,我永远无法知道我是否从对象类型U 中提取了键T ,或者是否是对象类型T ,我在那里提取了键U 。
做得更详细一点会有很大的帮助。
type Pick<Obj, Keys> = ...
注意:实际的Pick 类型在TypeScript中的定义要好得多(用K extends keyof T ),但你会明白这个意思。Exclude,Extract,Record...所有这些都让我挠头。
因此,尽管为我们的泛型使用单字母名称是很常见的,但我认为我们可以做得更好!
一个命名的概念#
类型是文档,我们的类型参数可以有说话的名字。就像你对普通函数所做的那样。这是我正在使用的风格指南。
- 所有的类型参数都以大写字母开始。就像我给所有其他类型起名字一样!
- 只有在用途完全清楚的情况下才使用单字母。例如:
ParseRouteParams只能有一个参数,即路线。 - 不要缩写为
T(那太......通用了!🤨),而要缩写为能让人清楚地知道我们在处理什么。例如:ParseRouteParams<R>,其中R代表Route。 - 很少使用单字母,坚持使用短语,或缩写。
Elem代表Element,Route可以保持原样。 - 在我需要区别于内置类型的地方使用前缀。例如,
Element,我可以使用GElement(或坚持使用Elem)。 - 使用前缀使通用名称更清晰
URLObj比Obj更清晰,例如。 - 同样的模式也适用于通用类型中的推断类型。
让我们再看一下ParseRouteParams ,用我们的名字更明确。
type ParseRouteParameters<Route> =
Route extends `${string}/:${infer Param}/${infer Rest}` ?
{ [Entry in Param | keyof ParseRouteParameters<`/${Rest}`>]: string } :
Route extends `${string}/:${infer Param}` ?
{ [Entry in Param]: string } : {}
每个类型的含义变得更清楚了。我们还看到,我们需要遍历Param 中的所有Entries ,即使Param 只是一个类型的集合。
可以说,比以前可读性强多了!