首页 > 解决方案 > 在方法中推断正确的字符串文字

问题描述

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

看看这个例子(操场链接):

// 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"一个类型。

我怎样才能做到这一点?

标签: typescript

解决方案


我不能肯定地告诉你为什么推理不能像你期望的那样工作,我希望它会这样。AllEvents<O, M>应该解决 的约束"1"|"2"|"3",这反过来应该让编译器推断E文字类型"1"。它反而推断"1"|"2"|"3".

从我的测试来看,问题在于使用ExtractinExtract<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">;

游乐场链接


推荐阅读