我想实现一种类型,它提取所有属性的内部类型是
Model<infer T>
,但不影响其他属性;像这样:
MyType<{ a: number, b: Model<string> }> // => { a: number, b: string }
我以为会这么简单:
type MyType<T> = {
[P in keyof T]: T[P] extends Model<infer R> ? R : T[P];
};
但是当这样测试时:
const fnForTesting = <T>(obj: T): MyType<T> => {
return null!;
};
const result = fnForTesting({
name: "Ann",
age: new Model<number>(),
});
const a = result.name; // unknown :( -> should be string
const b = result.age; // number -> works as expected
有人知道为什么“正常”属性没有被正确识别,而
Model
属性是吗?我该如何解决这个问题?提前致谢!
你的实现只检查属性类型是否扩展模型,但如果没有,它只是返回原始类型而不做任何修改。
要解决此问题,您可以更新 MyType 类型以包含一个包罗万象的案例,该案例返回任何不扩展模型的属性的原始类型:
type MyType<T> = {
[P in keyof T]: T[P] extends Model<infer R> ? R : T[P];
} & { [P in Exclude<keyof T, keyof Model<any>>]: T[P] };
这个更新的实现添加了第二个映射类型,其中包括所有不是模型的属性。 Exclude
由于您的模型类是空的,因此
Model<T>
与{}
(空对象类型)相同。因此,当您使用 T[P] extends Model<infer R>
时,TypeScript 无法正确推断出 R
,因此它使用 unknown
。 TypeScript 不回退并回退到 T[P]
的原因是因为 所有类型都可以分配 给 {}
除了 null
和 undefined
。基本上,
type T = string extends {} ? true : false;
// ^? true
现在请注意,当我向您的模型类添加属性以使其非空时,您的原始代码有效:
class Model<T>{
value!: T;
constructor() { }
}
// ...
const a = result.name; // string
// ^?
const b = result.age; // number
// ^?
const c = result.date; // date
// ^?
这是因为现在字符串(或日期)和
Model<T>
之间存在明显的结构差异,TypeScript 现在可以检查 T[P]
是否为模型并推断类型。
Filly 的代码有效,您实际上是在检查空对象是否可分配给无效的字符串(或日期)。在尝试推断模型的内部类型之前,这起到了保护作用。
type T = {} extends string ? true : false;
// ^? false
这意味着
Model<unknown> extends T[P]
仅在T[P]
是空对象或Model<T>
时触发。
(如果您的模型类在结构上与
{}
类型不同,则不需要 Filly 的解决方案)