簡體   English   中英

打字稿:對象的類型安全必須具有數組值中的所有鍵

[英]Typescript: typesafety for object must have all keys in array values

在 Typescript ^3.8 中,鑒於此接口...

interface IEndpoint { method: 'get'|'put'|'post'|'patch'|'delete', path: string }

而這個常數...

const endpoint = { method: 'get', path: '/first/:firstId/second/:secondId' }

請注意:firstId:secondId是將在運行時動態提供的路徑參數。 我有一個函數,它將獲取端點和一個帶有參數值的對象,並返回 url。

function buildEndpointUrl(endpoint: IEndpoint, map: {[key: string]: string}): string;

因此,例如:

// will set url to '/first/123/second/456'
const url = buildEndpointUrl(endpoint, {firstId: '123', secondId: '456'});

我面臨的挑戰是編譯器將允許垃圾作為第二個參數傳遞:如何定義IEndpointbuildEndpointUrl以便編譯器在作為第二個參數提供的對象缺少必需的鍵時拋出錯誤?

這是我嘗試過的:

interface IEndpoint<T extends ReadonlyArray<string>> { 
  method: 'get'|'put'|'post'|'patch'|'delete', 
  path: string
}

const endpoint: IEndpoint<['firstId', 'secondId']> = {...};

function buildEndpointUrl<T extends ReadonlyArray<string>>(
  endpoint: IEndpointConfig<T>, 
  map: {[key: T[number]]: string} // compiler error
);

最后一行拋出編譯器錯誤:

TS1023:索引簽名參數必須是“字符串”或“數字”

我希望T[number]string等效,因為T extends ReadonlyArray<string>但顯然不是。 我應該如何設置我的定義以添加類型安全?

您只需要一個映射類型而不是索引簽名。 預定義的映射類型Record將起作用

export interface IEndpoint<T extends ReadonlyArray<string>> { 
  method: 'get'|'put'|'post'|'patch'|'delete', 
  path: string
}

const endpoint: IEndpoint<['firstId', 'secondId']> =  { method: 'get', path: '/first/:firstId/second/:secondId' };

declare function buildEndpointUrl<T extends ReadonlyArray<string>>(
  endpoint: IEndpoint<T>, 
  map: Record<T[number],string> // compiler error
): void;

const b = buildEndpointUrl(endpoint, { firstId: "", secondId:"", test: "" })

游樂場鏈接

注意在 4.1 你也可以使用模板文字類型從路徑字符串中實際提取參數

export interface IEndpoint<T extends string> { 
  method: 'get'|'put'|'post'|'patch'|'delete', 
  path: T
}

type ExtractParameters<T extends string> = 
  T extends `${infer Prefix}/:${infer Param}/${infer Suffix}` ? Record<Param, string> & ExtractParameters<Suffix> & [Prefix, Suffix, Param] :
  T extends `${infer Prefix}/:${infer Param}` ? Record<Param, string>  :
  T extends `:${infer Param}`? Record<Param, string> :
  { T: T}

type X = "second/:secondId" extends `${infer Prefix}/:${infer Param}/${infer Suffix}` ? [Prefix, Param, Suffix] : "";
type Y = ExtractParameters<"/first/:firstId/second/:secondId">

const endpoint =  { method: 'get', path: '/first/:firstId/second/:secondId' } as const

declare function buildEndpointUrl<T extends string>(
  endpoint: IEndpoint<T>, 
  map: ExtractParameters<T>
): void;

const b = buildEndpointUrl(endpoint, { firstId: "", secondId:"", test: "" })

游樂場鏈接

你幾乎明白了:

type EndpointParams = ReadonlyArray<string>;

interface IEndpoint<T extends EndpointParams> { 
  method: 'get'|'put'|'post'|'patch'|'delete', 
  path: string
}

function buildEndpointUrl<T extends EndpointParams>(
  endpoint: IEndpoint<T>, 
  map: {[key in T[number]]: string} // In your case it should be mapped, not just indexed
) {}

const endpoint: IEndpoint<['first', 'second']> = {
    method: "get",
    path: "",
};

buildEndpointUrl(endpoint, { // failed
    first: "v1",
    p2: "v2",
});

buildEndpointUrl(endpoint, { // passed
    first: "v1",
    second: "v2",
});

暫無
暫無

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

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