问题
I have a method, that should accepts any object, as long as all its fields are strings or numbers
I made this, which works great with duck typing
static interpolateParams(
route: string,
params: {[key: string] : string | number}) : string {
const parts = route
.split("/")
.map(part => {
const match = part.match(/:([a-zA-Z09]*)\??/);
if (match) {
if (!params[match[1]]) {
console.error("route argument was not provided", route, match[1]);
return part;
}
return params[match[1]];
}
else {
return part;
}
})
return "/" + parts.slice(1).join("/");
}
and call
interpolateParams("/api/:method/:value", {method: "abc", value: 10});
Now I want to make interpolateParams
to accept any interface for route params
.
interpolateParams<IABCParams>("/api/:method/:value", {method: "abc", value: 10});
Problem is that it still should match constraints for all fields being strings or numbers
Is there a way to specify generic constraint on all fields of given interface to be of certain type?
I tried that
static interpolateParams<T extends {[key: string] : string | number}>(
route: string,
params: T) : string {
and obviously got this
Type 'IABCParams' does not satisfy the constraint '{ [key: string]: string | number; }'.
Index signature is missing in type 'IABCParams'.
Thanks
回答1:
T
's constraint can refer to T
(with some restrictions), so you can use a mapped type to generate a constraint that has the same fields as T
:
function interpolateParams<T extends {[P in keyof T] : string | number}>(
route: string,
params: T) : string { /*...*/ }
Beware that trickery with circular type parameter constraints can sometimes cause problems, though this scenario will likely be OK.
回答2:
Here is final version, thanks Matt for hint
static interpolateParams(
route: string,
params: {[key: string] : string | number}) : string {
const parts = route
.split("/")
.map(part => {
const match = part.match(/:([a-zA-Z09]*)\??/);
if (match) {
if (!params[match[1]]) {
if (part.endsWith("?")) {
return null;
}
console.error("route argument was not provided", route, match[1]);
return part;
}
return params[match[1]];
}
else {
return part;
}
}).filter(p => p && p != "");
return "/" + parts.join("/");
}
static formatRoute<T extends {[P in keyof T] : string | number}>(
route: string,
params: T
) : string {
return interpolateParams(route, params);
}
回答3:
If you want interpolateParams("/api/:method/:value", {method: "abc", value: 10});
to typecheck you can't. This is because you cannot infer anything about "/api/:method/:value"
to give you a method,value
signature.
Workaround
Write functions that take method
and value
and use it to power both the config and the use.
E.g. this is the strategy I use with takeme.
// Define
export const links = {
profile: (id: string) => `/profile/${id}`
}
// Use in Configure
links.profile(':profileId')
// Use in Navigate / a tags
links.profile(user.id)
来源:https://stackoverflow.com/questions/52730241/constraints-on-interface-members-in-typescript-generics