Typescript 获取对象属性的全路径作为类型
在使用 vue-i18n 时发现 t()
函数是有类型提示的,并且类型就是从 message
对象中推导出来的。于是我也尝试写一下这个类型推导。
假如有一个对象 message
:
const message = {
a: {
b: {
c: {
d: '1'
}
},
b1: '2'
}
}
从该对象中获取如下类型:
type KeyPath = 'a.b.c.d' | 'a.b1'
首先想到的写法是:
type PickupKey<T extends Record<string, any>, K = keyof T> = K extends string ? K : never
type KeyPath = PickupKey<typeof message> // => “a”
这个时候只拿到了第一层的属性,所以应该还需要一个递归,递归的时候需要将属性通过 .
拼接在一起:
type PickupPathKey<
T extends Record<string, any>,
K extends string,
M = keyof T
> = M extends string ? PickupPathKey<T[M], `${K}.${M}`> : K
type PickupKey<
T extends Record<string, any>,
K = keyof T
> = K extends string ? PickupPathKey<T[K], K> : never
type KeyPath = PickupKey<typeof message> // => “a.b.c.d” | "a.b1"
这样就实现了。但 PickupPathKey
与 PickupKey
很相似,是否可以将这两个合并成一个?可以试试先
type PickupPathKey<
T extends Record<string ,any>,
K extends string,
M = keyof T
> = M extends string ? PickupPathKey<T[M], `${K}.${M}`> : K
type KeyPath = PickupPathKey<typeof message, ''> // => “.a.b.c.d” | ".a.b1"
与预期的差不多,但前面都多了一个 .
。继续改造
type PickupPathKey<
T extends Record<string ,any>,
K extends (string | null) = null,
M = keyof T
> = M extends string ? PickupPathKey<T[M], (K extends string ? `${K}.${M}` : M)> : K
type KeyPath = PickupPathKey<typeof message> // => “a.b.c.d” | "a.b1"
与预期一致。这里借助了 null
类型和 K extends string
来判断是否是第一层的属性。