[英]Typescript parameters depending on each other
我不明白下面的錯誤
這是一個最小的可重現示例,帶有錯誤消息
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 })
}
我的 function 的正確類型是什么? 我正在嘗試使用 generics 讓 function 在多種類型上運行
這里發生了很多事情; 簡短的回答是,編譯器確實沒有能力在面對相關的聯合類型表達式時驗證類型安全。 考慮這段代碼:
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"
"T" | "U"
和b
作為類型"X" | "Y"
"X" | "Y"
,這兩個都是真的。 但是通過這樣做,它未能跟蹤它們之間的相關性。 所以[a, b]
被認為是類型["T", "X"] | ["T', "Y"] | ["U", "X"] | ["U", "Y"]
["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()
)或者使用類型斷言來讓編譯器靜音:
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"
"left" | "right"
。 因此foo()
和fooAssert()
的調用簽名與 fooEither fooEither()
) 中的調用簽名相同。 在這種情況下,如果我是你,我可能會退回到像fooSpread()
這樣的東西,至少可以安全地調用它。
相關值的問題經常出現,我已經提交了一個問題, microsoft/TypeScript#30581 ,建議在這里有比復制或斷言更好的東西。 las,沒有什么明顯的事情即將發生。
好的,希望有所幫助; 祝你好運!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.