简体   繁体   中英

Typescript: Does parameter order affect type inference?

I have the following types:

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) {
        /* ... */
    }
}

If I instantiate them like this:

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
);

The compiler complains that c is implicitly any and it's unable to resolve the type of S (it comes up as never ). Explicitly typing c fixes the issue, eg:

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

Why is that? Shouldn't it be able to infer C from the context parameter of the FSM constructor?

Wild Ass Guess : Does the order of the parameters in the FSM constructor matter? Is it that it tries to resolve the type of states first and does not yet know about the type of context ? If so is there a way to help the compiler "look ahead"?

NOTE : Another thing that I have a hard time understanding about this is that if I explicitly type c with a random type - eg transitions: (c:number)=>"a"; the compiler complains that c doesn't match the type of context. So the compiler knows what type c is but it needs me to show it that I know it too?

Parameter order can matter to inference if there are multiple candidates, but this is not that case. What you really want here is to use contextual typing, this is where the compiler infers the type of parameters (in this case) based on the type of where the function is to be assigned.

I am not sure of the mechanics of when contextual typing gives up, but if the assigned to type requires other type parameters there is a good chance it will stop working. A simple work around is to help contextual typing out by specifying an simpler type in the type of the parameter in an intersection with the type parameter. This will ensure we still capture in S the actual type passed in but we give contextual typing a simpler path to infer the parameter types:

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
);

Playground Link

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM