[英]Typescript Discriminated Unions typesafety not working on child object?
[英]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'});
我面临的挑战是编译器将允许垃圾作为第二个参数传递:如何定义IEndpoint
和buildEndpointUrl
以便编译器在作为第二个参数提供的对象缺少必需的键时抛出错误?
这是我尝试过的:
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.