gpt4 book ai didi

typescript 参数相互依赖

转载 作者:行者123 更新时间:2023-12-04 15:22:44 26 4
gpt4 key购买 nike

我不明白下面的错误

这是一个最小的可重现示例,带有错误消息

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!

这里变量 ab 是相关的:如果 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 })。没有什么可以限制 parentschild 正确关联。

为什么 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"| “正确”。因此 foo()fooAssert() 的调用签名与 fooEither() 中的调用签名相同。在这种情况下,如果我是你,我可能会退回到像 fooSpread() 这样的东西,至少可以安全地调用它。


相关值的问题经常出现,我已经提交了一个问题,microsoft/TypeScript#30581 ,这表明在这里有比重复或断言更好的东西会很好。唉,没有什么明显的事情即将发生。


好的,希望对你有帮助;祝你好运!

Playground link to code

关于 typescript 参数相互依赖,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62959293/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com