在方法中推断正确的字符串文字

时间:2019-02-01 18:41:40

标签: typescript

我需要说服Typescript来推断用户输入的字符串文字的帮助,而不是使用所有可能值的字符串文字。

看看这个例子(playground link):

// Supported methods.
type Methods = "GET" | "PUT" /* and etc. */;

// Event interface for each method.
interface Events {
    [key: string]: [any, any];
}

// Event objects assigned to methods.
type MethodicalEvents = {
    [key in Methods]: Events | undefined;
};

// Extract string keys only.
type EventKeys<E extends Events> = Extract<keyof E, string>;

// Extract all event keys from `MethodicalEvents -> Events`.
type AllEvents<O extends MethodicalEvents, M extends Methods> =
    O[M] extends object ? EventKeys<O[M]> : never;

// Extract send value (first array value in `Events` interface).
type ExtractSendValue<O extends MethodicalEvents, M extends Methods, E extends AllEvents<O, M>> =
    O[M] extends object ?
    O[M][E] extends [any, any] ?
    O[M][E][0] :
    never :
    never;

// Interface implementing stuff from above.
interface Foo extends MethodicalEvents {
    PUT: {
        "1": [123, void];
        "2": [string, void];
        "3": [boolean, void];
    };
}

// Class for making requests via `Foo` interface.
class Bar<O extends MethodicalEvents> {
    public request<M extends Methods, E extends AllEvents<O, M>>(
        method: M,
        event: E,
        data: ExtractSendValue<O, M, E>,
    ) {
        // Do stuff...
    }
}

const bar = new Bar<Foo>();
// `true` should not be allowed.
bar.request("PUT", "1", true /*type: `string | boolean | 123`*/);

// type is `123`, as expected
type ExpectedType = ExtractSendValue<Foo, "PUT", "1">;

bar.request的第二个参数的类型为"1" | "2" | "3",而我希望将"1"作为类型。

我该如何实现?

1 个答案:

答案 0 :(得分:1)

我不能肯定地告诉您为什么推论无法按您期望的那样进行,我希望如此。 AllEvents<O, M>应该满足"1"|"2"|"3"的约束条件,这反过来又应让编译器为E推断文字类型"1"。相反,它推断"1"|"2"|"3"

在我的测试中,问题出在Extract中使用Extract<keyof E, string>;,如果我们删除了该推断,则推断工作正常(尽管您的用例可能需要这样做)。

该错误的一种解决方法是重新排序AllEvent中的条件。这似乎可行:

// Supported methods.
type Methods = "GET" | "PUT" /* and etc. */;

// Event interface for each method.
interface Events {
    [key: string]: [any, any];
}

// Event objects assigned to methods.
type MethodicalEvents = {
    [key in Methods]: Events | undefined;
};

// Extract all event keys from `MethodicalEvents -> Events`.
type AllEvents<O extends MethodicalEvents, M extends Methods> = Extract<O[M] extends object ? keyof O[M] : never, string>

// Extract send value (first array value in `Events` interface).
type ExtractSendValue<O extends MethodicalEvents, M extends Methods, E extends AllEvents<O, M>> =
    O[M] extends object ?
    O[M][E] extends [any, any] ?
    O[M][E][0] :
    never :
    never;

// Interface implementing stuff from above.
interface Foo extends MethodicalEvents {
    PUT: {
        "1": [123, void];
        "2": [string, void];
        "3": [boolean, void];
    };
}

// Class for making requests via `Foo` interface.
class Bar<O extends MethodicalEvents> {
    public request<M extends Methods, E extends AllEvents<O, M>>(
        method: M,
        event: E,
        data: ExtractSendValue<O, M, E >,
    ) {
        // Do stuff...
    }
}

const bar = new Bar<Foo>();
// `true` is not allowed
bar.request("PUT", "1", true /*type: `string | boolean | 123`*/);

// type is `123`, as expected
type ExpectedType = ExtractSendValue<Foo, "PUT", "1">;

Playground link