Typescript: влияет ли порядок параметров на вывод типа? - PullRequest
2 голосов
/ 05 января 2020

У меня есть следующие типы:

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 из параметра context конструктора FSM?

Wild Ass Guess : соответствует ли порядок параметров в FSM конструктор имеет значение? Неужели он сначала пытается разрешить тип states и еще не знает о типе context? Если да, то есть ли способ помочь компилятору «заглянуть в будущее»?

ПРИМЕЧАНИЕ : Еще одна вещь, которую мне трудно понять по этому поводу, это то, что если я явно наберу c с случайный тип - например, transitions: (c:number)=>"a"; компилятор жалуется, что c не соответствует типу контекста. Таким образом, компилятор знает, что это за тип c, но ему нужно, чтобы я показал, что я тоже это знаю?

1 Ответ

3 голосов
/ 05 января 2020

Порядок параметров может иметь значение для вывода, если есть несколько кандидатов, но это не тот случай. Здесь вы действительно хотите использовать контекстную типизацию. Именно здесь компилятор определяет тип параметров (в данном случае) в зависимости от типа назначения функции.

Я не уверен в механике, когда контекстная типизация перестает работать, но если назначенный тип требует других параметров типа, есть большая вероятность, что он перестанет работать. Простой обходной путь - помочь контекстуальной типизации, указав более простой тип в типе параметра на пересечении с параметром типа. Это гарантирует, что мы по-прежнему фиксируем в 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
);

Playground Link

...