Typescript:对象的typesafety必须具有数组值中的所有键
在Typescript ^3.8中,给定此接口 接口IEndpoint{method:'get'|'put'|'post'|'patch'|'delete',path:string} 这个常数 const endpoint={method'get',path:'/first/:firstId/second/:secondId'} 请注意:firstId和:secondId是将在运行时动态提供的路径参数。我有一个函数,它将获取端点和带有param值的对象,并返回url 函数buildEndpointUrlendpoint:IEndpoint,映射:{[key:string]:string}:string; 例如: //将url设置为“/first/123/second/456” constURL=buildEndpointUrlendpoint,{firstId:'123',secondId:'456'}; 我面临的挑战是编译器将允许垃圾作为第二个参数传递:如何定义IEndpoint和buildEndpointUrl,以便在作为第二个参数提供的对象缺少必需的键时编译器抛出错误 以下是我尝试过的: 接口IEndpoint{ 方法:“获取”|“放置”|“发布”|“补丁”|“删除”, 路径:字符串 } 常量端点:IEndpoint={…}; 函数buildEndpointUrl 端点:IEndpointConfig, 映射:{[key:T[number]]:string}//编译器错误 ; 最后一行抛出编译器错误: TS1023:索引签名参数必须是字符串或数字 我希望T[number]与string等价,因为T扩展了ReadonlyArray,但显然不是。我应该如何设置我的定义以添加类型安全性?您几乎做到了:Typescript:对象的typesafety必须具有数组值中的所有键,typescript,Typescript,在Typescript ^3.8中,给定此接口 接口IEndpoint{method:'get'|'put'|'post'|'patch'|'delete',path:string} 这个常数 const endpoint={method'get',path:'/first/:firstId/second/:secondId'} 请注意:firstId和:secondId是将在运行时动态提供的路径参数。我有一个函数,它将获取端点和带有param值的对象,并返回url 函数buildEndpoin
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",
});
你几乎成功了:
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",
});
您只需要一个映射类型而不是索引签名。预定义的映射类型记录将起作用
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: "" })
您只需要一个映射类型而不是索引签名。预定义的映射类型记录将起作用
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: "" })
至少[key:T[number]]:string应该是[key:number]:string我认为这行不通。它不会强制参数具有所需的属性。在上面的示例中,如果我提供IEndpoint作为第一个函数参数,我希望Typescript推断第二个参数的类型必须是{firstId:string,secondId:string}如果调用看起来像buildEndpointUrlendpoint,['firstId',secondId'];?不第二个参数是用于将路由参数“firstId”的名称映射到其值“123”的对象。我在我的帖子中举了一个例子。我觉得这个问题和答案对你会很有用:至少[key:T[number]]:string应该是[key:number]:string我认为这行不通。它不会强制参数具有所需的属性。在上面的示例中,如果我提供IEndpoint作为第一个函数参数,我希望Typescript推断第二个参数的类型必须是{firstId:string,secondId:string}如果调用看起来像buildEndpointUrlendpoint,['firstId',secondId'];?不第二个参数是用于将路由参数“firstId”的名称映射到其值“123”的对象。我在我的帖子中举了一个例子。我觉得这个问题和答案对你很有用: