Constraints on interface members in typescript generics

廉价感情. 提交于 2019-12-20 01:52:45

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!