我不明白我在下面遇到的错误
这是一个最小的可复制示例,带有错误消息
type LeftChild = {
element: 0
}
type RightChild = {
element: 1
}
type Left = {
child: LeftChild
}
type Right = {
child: RightChild
}
interface Typings {
"left": {
parent: Left
child: LeftChild
}
"right": {
parent: Right
child: RightChild
}
}
function foo<T extends keyof Typings>(parents: Typings[T]["parent"][], child: Typings[T]["child"]) {
// Works
parents[0].child = child
// Doesn't work
parents.push({ child })
/* ^^^^^^^^^
Argument of type '{ child: Typings[T]["child"]; }' is not assignable to parameter of type 'Typings[T]["member"]'.
Type '{ child: Typings[T]["child"]; }' is not assignable to type 'Left | Right'.
Type '{ child: Typings[T]["child"]; }' is not assignable to type 'Right'.
Types of property 'child' are incompatible.
Type 'Typings[T]["child"]' is not assignable to type 'RightChild'.ts(2345)
*/
}
复制粘贴两种类型的实现时,效果都很好
function fooLeft(parents: Left[], child: LeftChild) {
parents[0].child = child
parents.push({ child })
}
function fooRight(parents: Right[], child: RightChild) {
parents[0].child = child
parents.push({ child })
}
什么是适合我的功能的类型?我正在尝试使用泛型来使函数在几种类型上运行
答案 0 :(得分:1)
这里发生了很多事情;简短的答案是,面对 correlated 联合类型表达式,编译器确实没有能力验证类型安全性。考虑以下代码:
declare const [a, b]: ["T", "X"] | ["U", "Y"]; // okay
const oops: ["T", "X"] | ["U", "Y"] = [a, b]; // error!
此处变量a
和b
是相关的:如果a
为"T"
,则b
必须为"X"
。否则,a
是"U"
,而b
是"Y"
。编译器将a
视为类型"T" | "U"
,将b
视为类型"X" | "Y"
,两者都是正确的。但是这样做无法跟踪它们之间的相关性。因此[a, b]
被认为是类型["T", "X"] | ["T', "Y"] | ["U", "X"] | ["U", "Y"]
。而且您会出错。
这或多或少是您正在发生的事情。我可以将您的代码重写为非通用版本,如下所示:
function fooEither(parents: Left[] | Right[], child: LeftChild | RightChild) {
parents[0].child = child; // no error, but unsafe!
parents.push({ child }) // error
}
fooEither([{ child: { element: 0 } }], { element: 1 }); // no error at compile time
在这里您可以看到编译器对parents[0].child = child
感到满意,这不应该...并且对parents.push({ child })
不满意。没有什么可以限制parents
和child
正确地关联。
parents[0].child = child
为什么起作用?好吧,编译器确实允许不正确的属性写入。例如,请参见microsoft/TypeScript#33911,并讨论为什么他们决定保持这种方式。
我可以尝试重写上面的代码以在调用站点上强制执行关联,但是编译器仍无法在实现中看到它:
function fooSpread(...[parents, child]: [Left[], LeftChild] | [Right[], RightChild]) {
parents[0].child = child; // same no error
parents.push({ child }) // same error
}
fooSpread([{ child: { element: 0 } }], { element: 1 }); // not allowed now, that's good
编译器没有某种方法可以维护联合类型之间的相关性,因此在这里没有太多要做。
我在这里看到的两个解决方法是复制代码(很像fooLeft()
和fooRight()
)或使用type assertions来使编译器静音:
function fooAssert<T extends keyof Typings>(parents: Typings[T]["parent"][], child: Typings[T]["child"]) {
parents[0].child = child
parents.push({ child } as Typings[T]["parent"]); // no error now
}
这可以编译,但是仅是因为您负责告诉编译器您所做的事情是安全的,而不是相反。这很危险,因为断言使您更有可能在未意识到的情况下做了不安全的事情:
fooAssert([{ child: { element: 0 } }], { element: 1 }); // look ma no error
// fooAssert<"left"|"right">(...);
糟糕,发生了什么事?好吧,您的呼叫签名在T
中是通用的,但是没有传入类型T
的参数。编译器无法为T
推断任何内容,而是将其扩展到其约束,这是"left" | "right"
。因此,foo()
和fooAssert()
的呼叫签名与fooEither()
中的呼叫签名是相同的不安全密钥。在这种情况下,如果我是您,我可能会退回到类似fooSpread()
的位置,至少可以安全地致电。
相关值的问题经常出现,以至于我提出了一个问题,microsoft/TypeScript#30581,这表明最好在这里提供比复制或断言更好的东西。 las,没有明显的结果。
好的,希望能有所帮助;祝你好运!