首页 > 解决方案 > 打字稿:参数顺序会影响类型推断吗?

问题描述

我有以下类型:

type State<C, S> = {
    onEnter?: (context: C) => void;
    onExit?: (context: C) => void;
    transitions?: (context: C) => S | undefined;
};

class FSM<C, S extends Record<keyof S, State<C, keyof S>>> {
    constructor(public readonly states: S, public readonly context: C) {
        /* ... */
    }
}

如果我像这样实例化它们:

const context = { foo: "bar" };
const f = new FSM(
    {
        a: { transitions: c => "b" }, // ERROR: c is implicitly any
        b: { transitions: c => "a" }  // ERROR: c is implicitly any
    },
    context
);

c编译器隐含地抱怨any它无法解析S(它出现为never)的类型。显式键入c可以解决问题,例如:

a: { transitions: (c:typeof context) => "b" },
b: { transitions: (c:typeof context) => "a" } 

这是为什么?它不应该能够从构造函数的参数中推断C出来吗?contextFSM

Wild Ass Guess:构造函数中参数的顺序是否FSM重要?是不是它试图先解决类型,states但还不知道类型context?如果是这样,有没有办法帮助编译器“向前看”?

注意:我很难理解的另一件事是,如果我显式c使用随机类型键入 - 例如transitions: (c:number)=>"a";编译器抱怨c与上下文类型不匹配。所以编译器知道什么是类型c,但它需要我证明我也知道吗?

标签: typescripttypescript-generics

解决方案


如果有多个候选者,参数顺序可能对推理很重要,但情况并非如此。您在这里真正想要的是使用上下文类型,这是编译器根据要分配函数的类型推断参数类型的地方(在这种情况下)。

我不确定上下文类型何时放弃的机制,但如果分配给类型需要其他类型参数,它很有可能会停止工作。一个简单的解决方法是通过在与类型参数的交集中指定参数类型中的更简单类型来帮助上下文输入。这将确保我们仍然捕获S传入的实际类型,但我们为上下文类型提供了一个更简单的路径来推断参数类型:

type State<C, S> = {
  onEnter?: (context: C) => void;
  onExit?: (context: C) => void;
  transitions?: (context: C) => S | undefined;
};

class FSM<C, S extends Record<keyof S, State<C, keyof S>>> {
  //  Record<string, State<C, keyof S>> added for contextual typing
  constructor(public readonly states: S & Record<string, State<C, keyof S>>, public readonly context: C) {
    /* ... */
  }
}

const context = { foo: "bar" };
const f = new FSM(
  {
    a: { transitions: (c) => c.foo != "" ? "a" : "b" },
    b: { transitions: (c) => "a" },
    c: { transitions: (c) => "d" }, // Error, d is not in the state keys
  },
  context
);

游乐场链接


推荐阅读