簡體   English   中英

Typescript個參數相互依賴

[英]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!

這里變量ab是相關的:如果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 })不滿意是正確的。 沒有什么可以限制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() )或者使用類型斷言來讓編譯器靜音:

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM