网站搜索

在 TypeScript 中以类型安全的方式调用函数


我认为一个例子胜过千言万语,所以下面是我正在尝试做的事情:

type Message = keyof MessageArguments;

type MessageArguments = {
    'foo': []
    'bar': {}
};

type MsgEvent<T extends Message> = {
  data: {
    type: T
    arguments: MessageArguments[T]
  }
};

type FunctionTypes = {
    [K in Message]: (event: MsgEvent<K>) => void 
}

const functions: FunctionTypes = {
    'bar': (event) => {
        console.log("invoked bar", event);
    },

    'foo': (event) => {
        console.log("invoked foo", event);
    }

};

function is<T extends Message>(e: MsgEvent<T>, type: T): e is MsgEvent<T> {
    return e.data.type === type;
}

function invoke<T extends Message>(e: MsgEvent<T>, type: T): FunctionTypes[T] {
    if (is(e, type)) {
        return functions[e.data.type](e);
    } else {
        throw "failed to find function";
    }
}

invoke({
    data: {
        type: 'foo',
        arguments: []
    }
}, 'foo');

如果你将它输入到 TypeScript 游乐场或编译器中,你会得到一个错误:

Cannot invoke an expression whose type lacks a call signature. Type '((event: MsgEvent<"foo">) => void) | ((event: MsgEvent<"bar">) => void)' has no compatible call signatures.
(property) type: T extends "foo" | "bar"

但是你可以很清楚地从代码中看出,只有某个方法(或者没有方法)可以被调用,我认为类型应该能够表示这一点,但我不知道怎么做……这几乎就像我需要告诉编译器在 function invoke<T extends Message> 中,T 是互斥的——它只能是这些值中的一个,而不是任意一个。

答案#1

沿着 Titian Cernicova-Dragomir 所说的思路,编译器并不知道你没有使用 T="foo" | "bar" 来调用 invoke。因此,它只能基于 T 的约束 "foo" | "bar" 进行推理。编辑:我刚刚更新了我之前的一个建议,以涵盖约束类型变量为单例的情况;我建议你为它投票。