TypeScript 随记 —— 如何推导出 interface 中值为string的key的类型
在使用TypeScript的时候,我们有时需要提取某个 interface/object 中某些类型的key的类型来作为高阶函数的参数或者某个函数的返回值。
下面的例子里,我们有一个用户的object,其具有一些基本属性和方法,现在我们希望对外提供一个方法来获取用户的某项信息。
在只使用TypeScript的基本特性时,你需要这样做:
interface User {
name: string
location: string
job: string
}
let user: User = {
name: 'ArrayZoneYour',
location: 'Beijing',
job: 'FE',
update: (info: User) => {
user = { ...user, ...info }
}
}
const getUserInfo: (key: 'name' | 'location' | 'job'): string => user[key]
看到这里我们需要手动标注属性为字符串的键的类型 'name'|'location'|'job'
,但是在user这个对象有很多的key的时候是很麻烦且不具有扩展性的。我们更希望可以有一个泛型方法来帮我们来提取这些key
const getUserInfo: (key: ExtractStringKey<typeof user>) => user[key]
那么如何实现这样一个方法呢?
思考空间
解决这个问题之前,我们先从另一个问题(如何在TypeScript 2.8之前实现Omit)的解决方案出发:
// Functionally the same as Exclude, but for strings only.
type Diff<T extends string, U extends string> = ({[P in T]: P } & {[P in U]: never } & { [x: string]: never })[T]
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>
注意上面的Diff
方法,它构造了一个 key: (key / never) 的object,T中出现过的key如果在U中出现了,key的value会被覆写为never,而此时再用所有的key [T]
去访问,可能获得的类型已经不包括被U覆写的key了。
那么我们是不是也可以构造两个object,原来的object的key的类型作为T,然后再生成一份object的key的类型,并将非string类型的key的值类型置为never
,然后再用[T]
去访问可获得的类型呢?
// 生成一份object的key的类型,并将非 T 继承类型的key的值类型置为 never
type ExtractType<O, T> = { [K in keyof O]: O[K] extends T ? O[K] : unknown }
// 组合两个 object 的类型
type Diff<T extends string, U> = ({ [P in T]: P } &
{ [P in keyof U]: U[P] extends string ? string : never } & {
[x: string]: never
})[T]
// 这里我们只以 string 类型的key作为示例
type ExtractStringKey<A> = Diff<
Extract<keyof A, string>,
ExtractType<A, string>
>
以浏览器的Location
类型为例Check一下:
replace, reload 是方法的键
Done.