【问题标题】:How to determine argument behavior of a function based on an interface property that is a union type?如何根据联合类型的接口属性确定函数的参数行为?
【发布时间】:2020-01-02 17:30:00
【问题描述】:

我有一个接口,它接受一个可以是函数类型或字符串的函数(对于异步函数,因此 tsc 不会转译它们)。问题出在我的任务运行器上,我似乎无法弄清楚如何根据接口上的 func 属性来确定函数的参数行为。它推断它为string | (...args: any[]) => number),例如func: (x: number) => x + 1)。问题在于,当我尝试检查 run 函数调用中的 func 参数以使其在接受参数的函数上需要参数时,它没有正确检查它是否扩展了函数,而是将参数推断为 any[] 而不是比number。有人知道怎么做这样的事情吗?

我在这里的意思的例子:

interface ITask<T> {
    id: number;
    func: ((...args: any[]) => T) | string;
};

class Task<T> implements ITask<T> {
    public id: number;
    public func: ((...args: any[]) => T) | string;

    constructor(opts: ITask<T>) {
        this.id = opts.id;
        this.func = opts.func;
    }
}

class Runner {
    constructor() { }
    public run<T>(task: { func: T }, ...args: T extends (...args: infer Args) => any ? Args : any[]) {
        if (typeof task.func === 'function')
            return task.func(...args);
        else
            return eval(`(${task.func})`);
    }
}

const task = new Task({ id: 1, func: (x: number) => x + 1 });

const runner = new Runner();
// should be expecting an argument, but func is not inferring from usage it's a function and not a string
runner.run(task);

TS Playground Link

【问题讨论】:

    标签: typescript


    【解决方案1】:

    如果您希望编译器充分记住 func 属性的类型以了解其参数是什么,则需要泛型类型参数来包含这些参数。你的ITask&lt;T&gt;T 只代表func 的返回类型,如果它是一个函数,参数类型是any[],这根本不是通用的。这是一种不同的可能类型,它能够跟踪函数参数和返回类型:

    interface ITask<F extends string | ((...args: any) => any)> {
        id: number;
        func: F;
    };
    

    这里的func 属性只是泛型类型F,而constrained 既可以是函数类型也可以是字符串。

    Task 的定义相应改变:

    class Task<F extends string | ((...args: any) => any)> implements ITask<F> {
        public id: number;
        public func: F
        constructor(opts: ITask<F>) {
            this.id = opts.id;
            this.func = opts.func;
        }
    }
    

    您的 Runner 类或多或少保持不变(不过,eval() 让我害怕)。您可能想要也可能不想更强烈地键入它的返回值(之前只是 any):

    class Runner {
        constructor() { }
        public run<T>(
            task: { func: T },
            ...args: T extends (...args: infer Args) => any ? Args : any[]
        ): T extends (...args: any) => infer R ? R : any {
            if (typeof task.func === 'function')
                return task.func(...args);
            else
                return eval(`(${task.func})`);
        }
    }
    

    现在你得到了这种行为:

    const task = new Task({ id: 1, func: (x: number) => x + 1 });
    const runner = new Runner();
    runner.run(task); // error! expected 2 arguments but got one
    const numVal = runner.run(task, 10); // okay
    // const numVal: number;
    
    const anyVal = runner.run(new Task({ id: 2, func: "someString" })); //okay
    // const anyVal: any;
    

    这一切在我看来都是合理的。好的,希望有帮助;祝你好运!

    Playground link to code

    【讨论】:

    猜你喜欢
    • 2023-03-30
    • 2022-09-23
    • 2019-11-26
    • 1970-01-01
    • 1970-01-01
    • 2021-11-13
    • 1970-01-01
    • 2013-04-11
    • 2022-11-17
    相关资源
    最近更新 更多