formatPerson(JsonBody<T extend Person>):T,可分配给类型"T"的约束,但可以使用不同的子类型实例化



我试图递归地将日期字符串格式化为日期对象,但我遇到了一个错误:

type JsonBody<T> = T extends Date
? string
: T extends (infer U)[]
? JsonBody<U>[]
: T extends object
? { [P in keyof T]: JsonBody<T[P]> }
: T;
type Person = {
name: string;
friend?: Person;
createdAt: Date;
};
type PersonWithFriend = Omit<Person, "friend"> & Required<Pick<Person, "friend">>;
function formatPerson<T extends Person>(body: JsonBody<T>): T {
return {
...body,
friend: body.friend && formatPerson(body.friend),
createdAt: new Date(body.createdAt)
};
// Type 'JsonBody<T> & { friend: Person | undefined; createdAt: Date; }' is not assignable to type 'T'.
//  'JsonBody<T> & { friend: Person | undefined; createdAt: Date; }' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Person'.
}
const one: JsonBody<Person> = { name: 'One', createdAt: '2021-01-21 00:59:11.07+00' };
const two: JsonBody<PersonWithFriend> = { name: 'One', friend: { name: 'Two', createdAt: '2021-01-21 00:59:11.07+00' }, createdAt: '2021-01-21 00:59:11.07+00' };
const oneFormatted = formatPerson(one).createdAt; // should be Date
const twoFormatted = formatPerson(two).friend.createdAt; // should be Date

参见游乐场

为什么formatPerson(JsonBody<T extend Person>)不返回T,为什么T不根据传递的参数变为PersonPersonWithFriend

感谢您提前提供的帮助。

问题是泛型函数的类型参数是由函数的调用者指定的,并且有多种方法可以扩展Person这样的类型。您可以添加新属性,但也可以缩小现有属性的类型。如果编译器无法验证实现是否符合调用方可能指定的内容,那么实现内部将出现编译器错误。

例如:

interface SpecialDate extends Date {
specialOccasion: string;
}
interface PersonWithSpecialDate extends Person {
createdAt: SpecialDate;
}
const expectedPersonWithSpecialDate =
formatPerson<PersonWithSpecialDate>({ name: "", createdAt: "" });
expectedPersonWithSpecialDate.createdAt.specialOccasion.toUpperCase(); // compiles, but 
// runtime error

在这里,由于一些只有自己知道的奇怪原因,调用者决定向formatPerson()请求PersonWithSpecialDate,其createdAt属性本身将具有string类型的specialOccasion属性。formatPerson()的呼叫签名声称能够做这样的事情,因为PersonWithSpeicalDate extends Person。因此,调用者会得到一些声称是PersonWithSpecialDate的东西,并且一切看起来都很好,直到它在运行时爆炸。

因此,实现中的警告是正确的。编译器告诉您,它不能确定您返回的内容是否可用作T。请参阅为什么我不能返回泛型't'来满足Partial<T>?关于这种情况的更明确的堆栈溢出问答(因为它来自TS团队领导(。


那么你能做什么呢?

这里最简单的方法就是告诉编译器你不关心这种可能性。是的,从技术上讲,T可能是一些你无法返回的奇怪东西,但它太不可能让你担心了。在这种情况下,您可以使用类型断言来表示";相信我,这是一个类型为T"的值:

function formatPerson<T extends Person>(body: JsonBody<T>): T {
return ({
...body,
friend: body.friend && formatPerson(body.friend),
createdAt: new Date(body.createdAt)
}) as T; // no error now
}

肯定有一种方法可以重写它,使呼叫签名完全准确(人们将无法要求它提供它无法提供的东西(,然而编译器可能仍然无法验证实现是否能满足它。编译器不太善于理解依赖于未指定泛型的条件类型的可赋值性(有关这方面可能的规范问题,请参阅microsoft/TypeScript#33912(。所以,我不想费力地去做这件事,而是到此为止。如果我发现一些足够简单的东西可以写出来,编译器可以实际验证,我可能会稍后回来编辑。

游乐场链接到代码

相关内容

最新更新