看到最近在讨论ts, 之前遇到了一个奇怪的问题,抛出来大家一起看看
Kntt opened this issue · 6 comments
Kntt commented
目标
实现一个 get方法, 功能 和 lodash里面的get一样
get(obj, key)
实现get的类型推导能力
- 根据obj, 可以推导出key的类型
- 推导出返回值的类型
遇到个奇怪的问题,
- 单独实现的get方法, 推导返回值类型总是出错, 如果基于class 实现个实例方法, get推导就一切正常
代码如下:
const data = {
name: 'Luis',
age: 18,
tel: 123456789,
address: {
city: 'beijing',
street: 'xiamen'
},
school: {
name: 'beijing',
tel: 123456789,
hasFee: true,
address: {
city: 'shanghai',
street: 'nightmen'
},
teacher: [1, 2, 3]
}
}
export class MyC {
data = {
name: 'Luis',
age: 18,
tel: 123456789,
address: {
city: 'beijing',
street: 'xiamen'
},
school: {
name: 'beijing',
tel: 123456789,
hasFee: true,
address: {
city: 'shanghai',
street: 'nightmen'
},
teacher: [1, 2, 3]
}
}
// @ts-ignore
get<P extends ObjectPropName<MyC['data']>>(path: P): ObjectPropType<MyC['data'], P> {
// @ts-ignore
return (path as string).split('.').reduce((a, b) => a[b], this.data);
}
}
type ObjectPropName<T, Path extends string = ''> = {
[K in keyof T]: K extends string
? T[K] extends Record<string, any>
? ObjectPath<Path, K> | ObjectPropName<T[K], ObjectPath<Path, K>>
: ObjectPath<Path, K>
: K;
}[keyof T];
type ObjectPath<Pre extends string, Curr extends string> = `${Pre extends '' ? Curr: `${Pre}.${Curr}`}`;
type ObjectPropType<T, Path extends string> =
Path extends keyof T
? T[Path]
: Path extends `${infer K}.${infer R}`
? K extends keyof T
? ObjectPropType<T[K], R>
: unknown
: unknown;
const c = new MyC();
// class 的原型方法, 可以使用 ObjectPropType 推导出返回值类型
const val = c.get('school.teacher');
type keys = ObjectPropName<typeof c.data>
// 定义单独方法 使用 ObjectPropType 推导返回值类型 都是 unknown
// @ts-ignore
const get = <O extends Record<string, any>, P extends ObjectPropName<O>>(o: O, path: P): ObjectPropType<O, P> => {
// @ts-ignore
return (path as string).split('.').reduce((a, b) => a[b], o);
}
// 单独使用 ObjectPropType 也可以推导出类型
type valueType = ObjectPropType<typeof c.data, 'school.teacher'>
// 这里的 v 推导出 unknown
const v = get(data, 'school.address');
waitingsong commented
@ts-ignore
越多,则结果非预期概率越大
netwjx commented
// 这里的 v 推导出 unknown
const v = get(data, 'school.address' as const);
/*
const v: {
city: string;
street: string;
}
*/
因为 ObjectPropType<typeof c.data, 'school.teacher'> 中 泛型 Path 的类型是 ’school.teacher‘
而 get(data, 'school.address') 对应到 ObjectPropType<T, Path> 中泛型 Path 的类型是 string
加上const 就会推导成字符串字面类型
Kntt commented
// 这里的 v 推导出 unknown const v = get(data, 'school.address' as const); /* const v: { city: string; street: string; } */因为 ObjectPropType<typeof c.data, 'school.teacher'> 中 泛型 Path 的类型是 ’school.teacher‘
而 get(data, 'school.address') 对应到 ObjectPropType<T, Path> 中泛型 Path 的类型是 string
加上const 就会推导成字符串字面类型
@netwjx 感谢解惑~
uinz commented
贴一个我的实现
type JoinDot<T extends string[], R extends string = ""> = T extends [
infer U extends string,
...infer V extends string[]
]
? JoinDot<V, R extends "" ? U : `${R}.${U}`>
: Exclude<R, "">;
export type ToPath<T, P extends string[] = []> = T extends unknown[]
? JoinDot<P> | ToPath<T[number], [...P, `${number}`]>
: T extends object
?
| JoinDot<P>
| {
[K in keyof T]-?: ToPath<T[K], [...P, K & string]>;
}[keyof T]
: JoinDot<P>;
export type Get<T, P> = P extends ""
? T
: P extends keyof T
? T[P]
: T extends unknown[]
? P extends `${number}`
? T[number]
: P extends `${infer L extends number}.${infer R}`
? Get<T[L], R>
: never
: P extends `${infer L}.${infer R}`
? L extends keyof T
? Get<T[L], R>
: undefined
: undefined;
Kntt commented
刚刚研究了一下:
如果
type Obj = {
a: {
b: {
c: { d: number }[]
}
},
e: {
f: string
},
g: number
f: {
[key: string]: any
}
}
如果如上的类型, 会有死循环的提示 Type instantiation is excessively deep and possibly infinite.
uinz commented
刚刚研究了一下:
如果
type Obj = { a: { b: { c: { d: number }[] } }, e: { f: string }, g: number f: { [key: string]: any } }如果如上的类型, 会有死循环的提示 Type instantiation is excessively deep and possibly infinite.
可以做一个深度检测,一般实际使用情况,不超过10层就好。