typescript - 打字稿参数相互依赖
问题描述
我不明白我在下面遇到的错误
这是一个最小的可重现示例,带有错误消息
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 })
}
什么是适合我的功能的类型?我正在尝试使用泛型让函数在几种类型上运行
解决方案
这里发生了很多事情;简短的回答是,面对相关的联合类型表达式,编译器确实没有能力验证类型安全。考虑这段代码:
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
type"T" | "U"
和b
as type"X" | "Y"
都是正确的。但是通过这样做,它未能跟踪它们之间的相关性。所以[a, b]
被认为是 type ["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()
and fooRight()
)或使用类型断言来使编译器静音:
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()
因此and的调用签名fooAssert()
与fooEither()
. 在这种情况下,如果我是你,我可能会退回到fooSpread()
至少可以安全调用的东西。
相关值的问题经常出现,以至于我提交了一个问题,microsoft/TypeScript#30581,表明在这里有比重复或断言更好的东西会很好。唉,没有什么明显的事情即将发生。
好的,希望有帮助;祝你好运!
推荐阅读
- variables - gforth:如何检查变量是否设置为 arg 值
- javascript - aspnet 中的谷歌图表
- html - css伪元素曲线背景定位
- javascript - Discord.js 缺少 API 权限 [防止节点进程被终止]
- mongodb - 如何构建 bson.D{}
- ios - 在我的 iOS 应用中使用自定义字体会影响应用大小吗?
- php - 我收到错误 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) with the command sudo mysql on ubuntu
- python - 如何在*自定义目录*下的 GitHub 页面上部署 mkdocs 站点
- javascript - 我需要从我的后端 JS 文件调用一个计算函数到我的前端 HTML 文件,它是一个按钮
- python - cv2.error : OPENCV(4.4.0) 错误(-215 断言失败) size.height>0 && size,width>0)