我正在尝试创建一组对T
必须实现并进行接口的泛型进行操作的函数IDocument
。虽然这似乎通常可行,但似乎TypeScript无法识别T必须具有键IDocument
。
这是一个最小的示例:
interface IDocument
{
_id: number;
}
function testGeneric<T>(v: { [P in keyof T]?: T[P] })
{ }
function testConstraint<T extends IDocument>(doc: T)
{
// this works
console.log(doc._id);
// this works
testGeneric<IDocument>({ _id: doc._id });
// this fails
// Argument of type '{ _id: number; }' is not assignable to parameter of type '{ [P in keyof T]?: T[P] | undefined; }'.
testGeneric<T>({ _id: doc._id });
}
您可以在这里的TypeScript游乐场现场观看。
我感到困惑,为什么这不起作用,因为在我的testConstraint
函数中好像T
总是有一个_id
密钥,因为它必须实现IDocument
。实际上,如果我接受一个T
参数并访问该_id
属性,它将可以正常工作。
请注意,该testGeneric
函数位于我不拥有的库中,因此无法更改该签名。
我在这里做错了什么?我是否需要使用其他约束来表达T
必须具有每个键的约束IDocument
?
原始示例的值类似于{_id: 10}
分配给泛型类型(如Partial<T>
where)T extends IDocument
。在这种情况下,您可以证明编译器是正确的,因为有些类型T
将扩展IDocument
到10
不是有效属性的地方。这基本上是与该问题相同的问题。
在您指定的新范例{_id: doc._id}
的泛型类型Partial<T>
,其中T extends IDocument
仍然产生错误,即使它绝对应该是安全的。编译器是无法验证这Pick<T, "_id">
是分配给Partial<T>
。这(涉及)一个未决问题; 编译器目前无法进行必要的类型分析来确保这一点。如果您确定某些内容是安全的(并且已经对它进行了两次和三次检查),但编译器却没有,那么您可能需要使用类型断言:
testGeneric<T>({ _id: doc._id } as Partial<T>); // okay now
因此,在实现的内部,testConstraint()
您可能需要使用类型声明或等效声明(例如,带有较宽松的实现签名的单个调用签名重载)。
最后,您说过,您实际上想阻止某人拨打电话testConstraint<T>()
,但T
其属性比的属性要窄IDocument
。这比T extends IDocument
Type更具限制性,并且在TypeScript中表示麻烦,在TypeScript中,属性变窄是子类型化的自然部分。您可以通过使泛型约束包含条件映射类型来做到这一点,如下所示:
function testConstraint<
T extends IDocument &
{ [K in keyof IDocument]: IDocument[K] extends T[K] ? T[K] : never }>(doc: T): void;
function testConstraint(doc: IDocument) {
testGeneric({ _id: doc._id });
}
在这里,我们将T
约束为IDocument
和,并将类型的每个属性IDocument
与的对应属性进行比较T
。如果一个范围T
不比一个范围窄,那就IDocument
好。否则,约束中的属性将一直缩小never
到,T
很可能不匹配。
调用签名非常复杂,以至于实现内部的类型确实会使编译器感到困惑。这就是为什么我将其重载,并将实现签名放松为完全非通用的原因。您可能可以对实现进行一些通用的操作,但是要点是,您可能应该根据类型分别对待调用方和实现。
让我们看看该函数的作用:
interface TheVeryFirstDocument extends IDocument {
_id: 1
}
declare const tv1d: TheVeryFirstDocument;
testConstraint(tv1d); // error!
// ---------> ~~~~~
// Types of property '_id' are incompatible.
// Type '1' is not assignable to type 'never'.(
正如我们所希望的那样,这会产生一个错误,而以下内容可以正常工作:
declare const doc: IDocument;
testConstraint(doc); // okay
interface ExtendedDocument extends IDocument {
title: string;
numPages: number;
}
declare const xDoc: ExtendedDocument;
testConstraint(xDoc); // okay
好吧,希望能有所帮助;祝好运!链接到代码
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句