这个 idea 是为我的聊天机器人自动化库创建一个有限状态机助手,以便与机器人交谈的人可以在对话的不同阶段进行.

我希望库的使用者为实例化状态机的函数提供"状态机描述符".

所有的行为都已经奏效了.我只是想为库的消费者改进静态类型.

这就是我想要实现的:

const myStateMachine = createStateMachine({
    initialState: "state1",
    states: {
        state1: {
            onMessage: (requester: MessageObj, stateMachine: StateMachineInstance) => {
                requester.reply("hello1");
                stateMachine.setState("state2");
            }
        },
        state2: {
            onMessage: (requester: MessageObj, stateMachine: StateMachineInstance) => {
                requester.reply("hello2");
                stateMachine.setState("state1");
            }
        }
    }
});

下面是我面临的问题:方法stateMachine.setState("state2")接受任何字符串,而不仅仅是描述符中提供的州的键.它应该接受"state1" | "state2",因为这些都是状态机应该具有的状态.

我试过很多不同的方法,但大多数都导致打字错误.我只是将它们恢复为一个泛型字符串,以便它可以编译.

以下是目前的类型:

type StateId = string;

type State =
    {
        onMessage: (
            requester: MessageObj,
            stateMachineInstance: StateMachineInstance
        ) => any;
    }

type StateMachineDescriptor = {
    initialState: StateId;
    states: {
        [stateId: string]: State,
    }
};

type StateMachineInstance = StateMachineDescriptor & {
    currentState: StateId;
    setState: (newState: StateId) => void;
    reset: () => void;
};

推荐答案

以下是我写一个可能的解决方案背后的整个思考过程:

由于我们使用了need个泛型来解决这个问题,所以我将继续使用一个来编写函数的签名:

declare function createStateMachine<States extends string>(value: StateMachineDescriptor<States>): StateMachineInstance<States>;

虽然您的could可能会使用States作为对象类型,但让它表示州名称要容易得多.我们遇到的第一个问题是Typescript 从错误的位置推断States.如果我们这样写:

type StateMachineDescriptor<States extends string> = {
    initialState: States;
    states: Record<States, State<States>>;
};

然后,TypeScrip将从initialState推断出States,这是错误的,因为我们希望initialState基于states,而不是相反.我们可以使用一种技巧来阻止initialState上的推理,将其向下移动到可能的推理站点列表中:

type NoInfer<T> = [T][T extends any ? 0 : never];

initialState分阻止推理,它按预期工作:

type StateMachineDescriptor<States extends string> = {
    initialState: NoInfer<States>;
    states: Record<States, State<States>>;
};

这确实是这里的主要问题.现在我们要做的就是添加一个通用参数:

type StateMachineInstance<States extends string> = StateMachineDescriptor<States> & {
    currentState: States;
    setState: (newState: States) => void;
    reset: () => void;
};

type State<States extends string> =
    {
        onMessage: (
            requester: MessageObj,
            stateMachineInstance: StateMachineInstance<States>
        ) => any;
    }

Playground

Typescript相关问答推荐

如何基于对象关键字派生类型?

TypScript ' NoInfer '类型未按预期工作

泛型函数类型验证

如果请求是流,如何等待所有响应块(Axios)

基于平台的重定向,Angular 为16

如何键入函数以只接受映射到其他一些特定类型的参数类型?

隐式键入脚本键映射使用

状态更新后未触发特定元素的Reaction CSS转换

ANGLE找不到辅助‘路由出口’的路由路径

使用TypeScrip根据(可选)属性推断结果类型

AngularJS服务不会解析传入的Json响应到管道中的模型

在排版修饰器中推断方法响应类型

常量类型参数回退类型

界面中数组中的混合类型导致打字错误

在Google授权后由客户端接收令牌

JSX.Element';不可分配给类型';ReactNode';React功能HOC

Karma Angular Unit测试如何创建未解析的promise并在单个测试中解决它,以判断当时的代码是否只在解决后运行

递归JSON类型脚本不接受 node 值中的变量

Typescript 是否可以区分泛型参数 void?

是否可以使用元组中对象的键来映射类型