簡體   English   中英

如何在 TypeScript 中使用已知和未知密鑰鍵入 object

[英]How do I type an object with known and unknown keys in TypeScript

我正在尋找一種方法來為以下 object 創建 TypeScript 類型,它具有兩個已知密鑰和一個具有已知類型的未知密鑰:

interface ComboObject {
  known: boolean
  field: number
  [U: string]: string
}

const comboObject: ComboObject = {
  known: true
  field: 123
  unknownName: 'value'
}

該代碼不起作用,因為 TypeScript 要求所有屬性都匹配給定索引簽名的類型。 但是,我不想使用索引簽名,我想輸入一個我知道其類型但不知道其名稱的字段。

到目前為止,我唯一的解決方案是使用索引簽名並設置所有可能類型的聯合類型:

interface ComboObject {
  [U: string]: boolean | number | string
}

但這有很多缺點,包括允許在已知字段上輸入不正確的類型以及允許任意數量的未知鍵。

有更好的方法嗎? TypeScript 2.8 條件類型有幫助嗎?

你自找的。

讓我們做一些類型操作來檢測給定的類型是否是聯合。 它的工作方式是使用條件類型的分配特性將聯合展開到組成部分,然后注意到每個組成部分都比聯合窄。 如果這不是真的,那是因為聯合只有一個組成部分(所以它不是聯合):

type IsAUnion<T, Y = true, N = false, U = T> = U extends any
  ? ([T] extends [U] ? N : Y)
  : never;

然后使用它來檢測給定的string類型是否是單個字符串文字(因此:不是string ,不是never ,也不是聯合):

type IsASingleStringLiteral<
  T extends string,
  Y = true,
  N = false
> = string extends T ? N : [T] extends [never] ? N : IsAUnion<T, N, Y>;

現在我們可以開始處理您的特定問題。 定義BaseObject作為部分ComboObject ,你可以直截了當地定義:

type BaseObject = { known: boolean, field: number };

並為錯誤消息做准備,讓我們定義一個ProperComboObject以便當您搞砸時,錯誤會提示您應該做什么:

interface ProperComboObject extends BaseObject {
  '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!': string
}

主菜來了。 VerifyComboObject<C>接受一個C類型,如果它符合您想要的ComboObject類型,則將它原封不動地返回; 否則,它會為錯誤返回ProperComboObject (它也不符合)。

type VerifyComboObject<
  C,
  X extends string = Extract<Exclude<keyof C, keyof BaseObject>, string>
> = C extends BaseObject & Record<X, string>
  ? IsASingleStringLiteral<X, C, ProperComboObject>
  : ProperComboObject;

它的工作原理是將C分解為BaseObject和其余的鍵X 如果CBaseObject & Record<X, string>不匹配,那么您就失敗了,因為這意味着它要么不是BaseObject ,要么是具有額外非string屬性的對象。 然后,它通過使用IsASingleStringLiteral<X>檢查XIsASingleStringLiteral<X>一個鍵。

現在我們創建一個輔助函數,它要求輸入參數匹配VerifyComboObject<C> ,並返回未更改的輸入。 如果您只想要一個正確類型的對象,它可以讓您及早發現錯誤。 或者你可以使用簽名來幫助你自己的函數需要正確的類型:

const asComboObject = <C>(x: C & VerifyComboObject<C>): C => x;

讓我們測試一下:

const okayComboObject = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value'
}); // okay

const wrongExtraKey = asComboObject({
  known: true,
  field: 123,
  unknownName: 3
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const missingExtraKey = asComboObject({
  known: true,
  field: 123
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

const tooManyExtraKeys = asComboObject({
  known: true,
  field: 123,
  unknownName: 'value',
  anAdditionalName: 'value'
}); // error, '!!!ExactlyOneOtherStringPropertyNoMoreNoLess!!!' is missing

第一個編譯,根據需要。 最后三個失敗的原因不同,與額外屬性的數量和類型有關。 錯誤消息有點神秘,但這是我能做的最好的。

您可以在Playground 中看到正在運行的代碼。


同樣,我認為我不建議將其用於生產代碼。 我喜歡玩弄類型系統,但感覺這個系統特別復雜和脆弱,我不想為任何不可預見的后果負責。

希望對你有幫助。 祝你好運!

不錯的一個@jcalz

它給了我一些很好的洞察力,可以到達我想要的地方。 我有一個具有一些已知屬性的 BaseObject,並且 BaseObject 可以擁有任意數量的 BaseObject。

type BaseObject = { known: boolean, field: number };
type CoolType<C, X extends string | number | symbol = Exclude<keyof C, keyof BaseObject>> = BaseObject & Record<X, BaseObject>;
const asComboObject = <C>(x: C & CoolType<C>): C => x;

const tooManyExtraKeys = asComboObject({
     known: true,
     field: 123,
     unknownName: {
         known: false,
         field: 333
     },
     anAdditionalName: {
         known: true,
         field: 444
     },
});

這樣我就可以對我已經擁有的結構進行類型檢查,而無需進行太多更改。

我提出了一個類似的問題,我認為@matthew-dean 的建議要簡單得多,並且可以解決許多類似的問題。 它對我有用,所以我認為它應該被稱為可能的解決方案:

interface ComboObject {
  known: boolean
  field: number
  [key: string]: string
}

const comboObject: ComboObject = {
  known: true
  field: 123
  unknownName: 'value'
}

通過這種方法,您可以指定已知字段的類型並修復其余字段的類型。

使用此方法未完成的唯一要求是您不限制未知字段的數量。 為此,您將需要與其他答復中建議的方法不同的方法。

在我看來,更簡單的方法是使用聯合類型:

type ComboObject = {
  known: boolean
  field: number
} & {
  [key: string]: string | number | boolean;
};

這告訴 TypeScript 從左邊繼承,但是當你向它提供額外的未知參數類型時不會生氣。

暫無
暫無

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

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