Typescript温故而知新
昨天下午上班摸鱼的时候,突然看到群里有人在学Typescript,问我怎么学。我说多用,然后一想,反正在摸鱼,不如复习一下Typescript(装一下比)。
装逼结果
首先咱们先来复习一下基础知识,先从泛型讲到工具类型
泛型
type Required<T> = {
[K in keyof T]: T[K]
}
type Partial<T> = {
[K in keyof T]?: T[K]
}
以上是泛型的最基本的用法之一,这里的keyof
是Typescript的关键字,用于将对象类型的key展开作为联合类型
。另外有一个关键字typeof
就是用于得到某个对象的类型
我们这里进行一个简单的组合,如果我部分字段想要必填,另外一些字段想要非必填怎么办呢?
type RequiredExcept<T, U extends keyof T> = {
[K in keyof Pick<T, U>]?: T[K]
} & {
[K in keyof Omit<T, U>]: T[K]
}
type User = {
name: string
age: number
gender: boolean
}
type Test = RequiredExcept<User, 'name'>
const user: Test = {
age: 1,
gender: true
}
工具类型
工具类型指的是那些将泛型作为参数达到某种目的而被广泛使用的类型,比如我们刚刚写的Required
、Partial
、Pick
、Omit
。常见的工具类型比如Record
、ReturnType
、Lowercase
等。这里我们介绍几种了解
Record
Record
常见于定义键值对的对象的类型。
type Record<K extends string | number | symbol, T> = {
[P in K]: T
}
Pick和Omit
Pick
和Omit
是一对可以起用的,两者刚好相反,前者取得一个类型中的部分字段组合成为一个新类型;后者取得一个类型中的除了所罗列的字段之外的字段组合起来作为新类型
type Pick<T, U extends keyof T> = {
[K in U]: T[K]
}
type Omit<T, U extends string | number | symbol> = {
[K in Exclude<keyof T, U>]: T[K]
}
type Exclude<T, U> = T extends U ? never : T
type User = {
name: string
age: number
gender: boolean
}
type NewUser = Pick1<User, 'age' | 'name'>
type NewUser2 = Omit<User, 'age' | 'name'>
可以看到Omit
借助了新的工具类型Exclude
才实现的,Exclude
从定义就可以看出,如果T是U的子类型则返回never(无类型),否则返回T。这就可以将一个类型中在所给定的联合类型里的字段给剔除掉,留下没有在联合类型的字段组合成新的类型
模板字符串类型
没想到吧,模板字符串竟然也能当作类型。普通字符串都能作为类型使用,模板字符串也可以作为类型使用
const user = 'ainuo5213'
type User = typeof user
以上会返回什么类型?我想不熟悉Typescript的大多会说字符串类型。错了,大错特错,字符串类型所表示的字符串是可以更改的,这里的user
很明显不可更改,所以他应该是"ainuo5213"
类型,这样一个字符串就确定了其不可变。
那么模板字符串怎么写类型呢?很简单
type Hello<T extends string> = `Hello ${T}`
type HelloWord = Hello<'world'> // Hello world
当然,模板字符串类型也可以和联合类型组合使用,就像下面这样
type MobileBrand = 'iphone' | 'huawei'
type Memory = '16g' | '32g'
type Mobile = `${MobileBrand}-${Memory}`
他会得到如下的组合结果
我们发现,联合类型分别被组合了,这样我们就得到了一个通用的定义手机的类型
如果我们想定义版本号,比如v1.0.1
,也可以用模板字符串进行书写
type AlphaOrBeta = 'alpha' | 'beta'
type Suffix = `-${AlphaOrBeta}.${number}`
type Version = `v${number}.${number}.${number}${Suffix}`
const version: Version = 'v1.0.0-beta.1'
针对字符串类型的工具类型
常用的针对字符串类型的工具类型有Capitalize
、Uncapitalize
、Uppercase
、Lowercase
,他们分别是针对字符串的大小写等进行操作,比如我们可以这样使用
type User = {
name: string
age: number
}
type ChangeEvent<T> = {
[K in keyof T as `on${Capitalize<K & string>}Change`]: (newValue: T[K]) => void
}
type UserChangeEvent = ChangeEvent<User>
上面这个例子通过改变映射之后的K的值,和Capitalize
进行组合使用就得到了一个ChangeEvent
。我们可以查看一下其结果
infer
infer在大多数的Typescript编程过程中用不到,但是Typescript既然引入了他就必然有其意义
infer可以对类型进行推断,且只能用于类型推断。他可以构建如常用的ReturnType
、ParameterType
这些对函数类型中返回值和参数的类型推断
type AsyncFunc<T, U> = (...args: T[]) => Promise<U>
type User = {
name: string
age: number
}
type GetUserAsyncFunc = AsyncFunc<number, User>
type ParameterType<T> = T extends (...args: [infer R]) => any ? R : never
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type GetUserAsyncFuncParameterType = ParameterType<GetUserAsyncFunc> // number
type GetUserAsyncFuncReturnType = ReturnType<GetUserAsyncFunc> // Promise<User>
就这样我们可以提取得到一个函数的参数和返回值的类型
infer用法很多样,这里可以去林不渡大佬的小册子进行阅读: [类型里的逻辑运算:条件类型与 infer](juejin.cn/book/708640…)
回归主线,实现ParsedEnvProcessType
了解了infer
等相关知识,我们可以着手急性ParsedEnvProcessType
的类型编写。
ParsedEnvProcessType
是一个将以REACT_APP_
开头的环境变量书写成小驼峰形式。了解了之后,开始装逼过程
将前缀去掉
type ProcessEnv = {
REACT_APP_ENV: string
REACT_APP_BASE_URL: string
NODE_ENV: string
REACT_APP_GRAY_TITLE: boolean
}
type PrunePrefix<T, U extends string> = {
[K in keyof T as K extends `${U}${infer R}` ? R : K]: T[K]
}
type PrunedPrefixProcessEnv = PrunePrefix<ProcessEnv, 'REACT_APP_'>
看一下结果是否符合预期
看来还不错,继续
将所有类型字段变成小写
将类型变成小写,就可以结合之前的Lowercase
了,全面的知识也串起来了
type ProcessEnv = {
REACT_APP_ENV: string
REACT_APP_BASE_URL: string
NODE_ENV: string
REACT_APP_GRAY_TITLE: boolean
}
type PrunePrefix<T, U extends string> = {
[K in keyof T as K extends `${U}${infer R}` ? R : K]: T[K]
}
type PrunedPrefixProcessEnv = PrunePrefix<ProcessEnv, 'REACT_APP_'>
type LowserCased<T> = {
[K in keyof T as Lowercase<K & string>]: T[K]
}
type LowerCasedPrunedPrefixProcessEnv = LowserCased<PrunedPrefixProcessEnv>
这下看看结果对不对
还可以,那么咱们 继续
去掉下划线并将下划线首字母大写
去掉下划线的时候,由于不知道字符串有多少个_
,所以这里咱们不能很死板的写。
既然我们想要循环去掉_
,那能不能用js中的递归呢?答案是肯定的,可以!!!
type Camel<T extends string> = T extends `_${infer R}`
? Camel<R>
: T extends `${infer R}_${infer U}`
? `${Uncapitalize<R>}${Capitalize<Camel<U>>}`
: T
type CameledBaseUrl = Camel<'gray_title_test'>
在这个代码中,我们首先对T
是否是_
开头做了推断,如果是以_
开头的话,就递归之后的R
;否则的话,T就不是以_
开头,走到了第二个分支。第二个分支对T
是否是xx_yy
这种类型做了判断,如果是这样的话,我们将R
进行小写开头,并将U
进行递归,并将最后的值大写开头,这样咱们就得到了一个小驼峰写法的类型了。
可以见一下结果对不对
将小驼峰和其他代码组合
type ProcessEnv = {
REACT_APP_ENV: string
REACT_APP_BASE_URL: string
NODE_ENV: string
REACT_APP_GRAY_TITLE: boolean
}
type PrunePrefix<T, U extends string> = {
[K in keyof T as K extends `${U}${infer R}` ? R : K]: T[K]
}
type PrunedPrefixProcessEnv = PrunePrefix<ProcessEnv, 'REACT_APP_'>
type LowserCased<T> = {
[K in keyof T as Lowercase<K & string>]: T[K]
}
type LowerCasedPrunedPrefixProcessEnv = LowserCased<PrunedPrefixProcessEnv>
type Camel<T extends string> = T extends `_${infer R}`
? Camel<R>
: T extends `${infer R}_${infer U}`
? `${Uncapitalize<R>}${Capitalize<Camel<U>>}`
: T
type CamelCase<T> = {
[K in keyof T as Camel<K & string>]: T[K]
}
type ParsedProcessEnvType = CamelCase<LowserCased<PrunePrefix<ProcessEnv, 'REACT_APP_'>>>
CamelCase将类型中的K转成驼峰,然后组合成新的类型。这样咱们就得到了一个小驼峰之后的环境变量类型。
检验一下结果对不对
发现也是对的,这样咱们的比就装完了。哈哈哈
转载自:https://juejin.cn/post/7218208891707146277