簡體   English   中英

Typescript 動態 object 類型定義

[英]Typescript dynamic object type definition

假設我有一個 object:

const a = {
    section_title: 'Hello world',
    section_desc: 'Lorem ipsum dolor sit amet'
}

以及一個通過“前綴”查找所有屬性並返回一個新的 object 的實用程序,這些屬性沒有“前綴” (即 section_title -> title)

function findPropsByPrefix(data, prefix) {
  return Object.keys(data)
    .filter((key) => key.startsWith(prefix))
    .reduce((prev, curr) => {
      prev[curr.substr(prefix.length)] = data[curr]
      return prev
    }, {})
}

// Convert { section_title: '...', section_desc: '...' } 
// into { title: '...', desc: '...' }
const b = findPropsByPrefix(a, 'section_')

我應該如何輸入實用程序 function findPropsByPrefix

這個 Typescript 操場上,我正在嘗試使用Record<string, any>

function findPropsByPrefix(data: Record<string, any>, prefix: string): Record<string, any> {
  ...
}

但這會導致錯誤Type 'Record<string, any>' is missing the following properties from type 'B': title, desc當我將返回值分配給b時。

extractFieldsByPrefix()的調用簽名應該是這樣的:

// call signature
function extractFieldsByPrefix<P extends string, T extends Record<string, any>>(
  data: T, prefix: P): { [K in keyof T as K extends `${P}${infer R}` ? R : never]: T[K] };

為了跟蹤特定的鍵和值,function 在prefix類型Pdata類型T中都是通用的。 返回類型是使用模板文字類型鍵重映射TP計算出來的。 讓我們檢查一下:

{ [K in keyof T as K extends `${P}${infer R}` ? R : never]: T[K] }

這是一個映射類型,它從T的鍵中查看每個鍵K 對於每個這樣的鍵,它使用條件類型K extends... ? ... : ... )來嘗試查看該鍵是否以前綴P開頭,如果是,則使用條件類型推斷來infer后綴R`${P}${infer R}`是一個模板文字類型,表示您在將P連接到R時獲得的字符串(對於某些R )。 這是因為模板文字類型支持這種推斷 如果推理成功,那么我們將舊密鑰K重新映射到新密鑰R (所以只是后綴)。 如果推理失敗,那么我們將舊的K鍵重新映射到never ,這具有完全刪除該屬性的效果。 屬性值類型只是T[K] ,因此R的新屬性將與K的舊屬性具有相同的類型。


編譯器通常不可能驗證 function 實現是否滿足具有條件或重新映射類型 output 的通用調用簽名; 有關相關問題,請參閱microsoft/TypeScript#33912 現在最好的辦法是使用類似單調用簽名重載的東西,以便對實現進行一些松散的檢查:

// implementation
function extractFieldsByPrefix(data: any, prefix: string) {
  return Object.keys(data)
    .filter((key) => key.startsWith(prefix))
    .reduce<any>((prev, curr) => {
      prev[curr.substring(prefix.length)] = data[curr]
      return prev
    }, {})
}

通過將datareduce()的結果都提供給any type ,我實質上是在說我不希望編譯器抱怨任何可能的類型問題。 這是最容易做的事情,但這確實意味着您需要小心該實現,因為缺少編譯器錯誤並不意味着該實現沒有錯誤。 例如,如果您編寫了endsWith()而不是startsWith() ,編譯器仍然不會抱怨,但現在運行時的 output 將不符合調用簽名的返回類型。 所以要小心。


無論如何,讓我們看看它是否有效:

const result = extractFieldsByPrefix(a, 'section_');
/* const result: {
    title: string;
    desc: string;
} */
console.log(result.title.toUpperCase()) // HELLO WORLD!

const b: B = result; // okay
console.log(b)
/* {
  "title": "Hello world!",
  "desc": "Lorem ipsum dolor sit amet"
} */

// make sure that dropping fields without the prefix also happens
const also = extractFieldsByPrefix(
  { who: 1, what: 2, when: 3, where: 4, why: 5, how: 6 },
  "w"
);
/* const also: {
    ho: number;
    hat: number;
    hen: number;
    here: number;
    hy: number;
} */
console.log(also);
/* {
  "ho": 1,
  "hat": 2,
  "hen": 3,
  "here": 4,
  "hy": 5
}  */

看起來不錯! Playground 代碼鏈接

暫無
暫無

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

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