簡體   English   中英

你能在 TypeScript 中指定一個對象字面量的類型嗎?

[英]Can you specify an object literal's type in TypeScript?

我想知道是否有辦法指定對象文字的類型。

例如,如何解析此代碼並將B文字分配給A變量:

interface A {
    a: string;
}

interface B extends A {
    b: string
}

const a: A = {
    a: "",
    b: ""
};

BA ,所以我希望能夠在需要A地方分配B 但是,編譯器沒有足夠的上下文來確定我傳遞的是B而不是非法的A ,因此我需要指定它。

我不想只編譯上面的代碼,我特別希望將“我將B分配給A ”的意圖傳達給編譯器。 我想要具有B對象文字的類型安全性,因此如果B獲得其他屬性,例如,這應該無法再次編譯。

我在搜索這個時偶然發現了這篇文章 一種選擇是定義一個恆等函數:

const is = <T>(x: T): T => x;

const a: A = is<B>({
    a: "",
    b: ""
});

這並不理想,因為它會為了一些在轉譯后無關緊要的東西而運行代碼。 另一種選擇是使用第二個變量:

const b: B = {
    a: "",
    b: ""
};

const a: A = b;

出於同樣的原因也不理想,它是為了 TypeScript 檢查而編寫的代碼。

@jcalz 的建議:

interface A {
  a: string;
  [k: string]: unknown; // any other property is fine
}

當我只想通過A及其子類型時,這允許通過任何多余的屬性。 A的封閉性不是問題,我只是無法告訴編譯器我在A預期的地方傳遞了B

Turn off --suppressExcessPropertyErrors compiler option

我想抑制多余的屬性,問題是我無法告訴編譯器這些不是多余的屬性,它們是正確子類型的屬性。

是否有等價於文字的類型聲明? 是否有 TypeScript 的基本設計可以阻止這種構造的存在?

在 Kotlin 等語言中,這是微不足道的:

val a: A = B()

可以分別聲明變量的類型和運行時對象的類型(必須是相同類型或子類型)。

如果這是一個XY 問題,我最初試圖解決的問題如下所示:

const foo = (): A => {
    if (/* condition */) {
        /* some setup.. */
        return {
            a: "",
            b: ""
        }
    } else {
        return {
            a: ""
        }
    }
}

這不會編譯,因為第一個return不是明確的A ,它旨在成為BA ,但我無法在不訴諸我上面提到的解決方法之一的情況下將其指定為B 我想要看起來像的東西

return {
    a: "",
    b: ""
}: B

// or

return: B {
    a: "",
    b: ""
}

// or

return {
    a: "",
    b: ""
} is B

不用說, as B類型的斷言不是我在這里尋找的。 我不想繞過類型系統,我只想幫助編譯器檢查我的對象文字是否為指定類型。 這個問題也適用於參數:

const foo = (a: A) => {};

foo({ a: "", b: "" })

這失敗了,因為我無法告訴編譯器我正在傳遞B ,它認為我使用了多余的屬性。 它僅在我使用像這樣的額外變量時才有效:

const b: B = { a: "", b: "" };

foo(b)

更新:這里的主要問題似乎是 TypeScript 沒有“僅擴展斷言運算符”或“內聯類型注釋運算符”(例如, return {a: 123} op A應該失敗但return {a: "", b: ""} op A應該成功)。 有一個開放的建議,請參閱 microsoft/TypeScript#7481,但那里沒有真正的動向。 如果您想看到這種情況發生,您可能想要轉到那個問題並給它一個 👍 和/或描述您的用例,如果它比現有的更引人注目。 在沒有明確的類型運算符的情況下,有可用的策略和解決方法。


聽起來您已經知道處理此問題的最常見選項,但您不喜歡其中任何一個。 我還有一些其他的事情你可以嘗試,但如果你的限制是:

  • 沒有更改您的類型定義或注釋
  • 對發出的運行時代碼沒有更改
  • 沒有類型斷言

那么您的選擇確實有限,您可能找不到任何令人滿意的答案。


正在發生的事情的背景:

對象字面量是 TypeScript 的正常可擴展對象類型的一個有趣的例外。 通常,TypeScript 中的對象類型並不精確 {a: string}這樣的類型定義只要求一個值必須有一組已知的屬性。 它不要求值必須缺少未知屬性 所以一個對象{a: "", b: 123, c: true}是一個完全有效的{a: string}類型的值,因為它有a屬性,其值可以賦值給string 所以看起來你的代碼應該沒有什么; 您的a確實是有效的A ,無論是否有上下文。

但當然編譯器會抱怨。 那是因為您已經將一個新的對象字面量分配給一個變量,該變量的類型注釋的已知屬性比對象字面量少。 因此它與過度屬性檢查相沖突,其中對象類型被視為精確。 您可以閱讀實現此功能的拉取請求以及執行此操作基本原理 這些問題還詳細說明了針對不需要進行過多屬性檢查的情況的建議解決方法。


您已經提到創建一個輔助函數(例如, return is<A>({...}) ),分配給一個中間變量(例如, const ret = {...}; return ret; ),並使用類型斷言(例如, return {...} as A )。 以下是我看到的其他選項:

  • 為您的A類型添加索引簽名,以便它顯式打開:

     interface A { a: string; [k: string]: unknown; // any other property is fine } const a: A = { a: "", b: "" }; // okay now
  • 使用--suppressExcessPropertyErrors編譯器選項完全關閉多余的屬性檢查(我真的不推薦這樣做,因為它是一個范圍廣泛的變化)

  • 使用聯合類型,A | B 注釋對象字面量對應的類型時,用A | B代替A

     const a: A | B = { a: "", b: "" }; // okay now

    由於BA的子類型,因此類型A | B A | B在結構上等同於A ,因此您幾乎可以在任何地方使用它代替A

     const foo = (): A | B => { ... } // your impl here function onlyAcceptA(a: A) { } onlyAcceptA(foo()); // okay
  • 僅在額外屬性上使用類型斷言,這需要使用計算屬性名稱

     const a: A = { a: "", ["b" as string]: "" }; // okay

    這確實沒有任何類型安全性降低,但它最終會生成略有不同的 JavaScript( {a: "", ["b"]: ""} ),假設您的目標是支持計算屬性名稱的 JavaScript 版本.

我能想到的其他一切只是對您的運行時代碼進行了更多更改,因此我將就此打住。


無論如何,希望能給你一些想法。 祝你好運!

代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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