繁体   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