Как передать потоку карту объекта так, чтобы значение содержало ключ - PullRequest
0 голосов
/ 05 марта 2019

Я пытаюсь правильно набрать (используя Flow) вспомогательную функцию createReducer для редукса.Я использовал код из redux-immutablejs в качестве отправной точки.

Я пытаюсь следовать совету из документооборота по вводу редукторных редукторов ,но оберните его в вспомогательную функцию со стилем записи картыОсновная идея:

Создайте отдельный тип для каждого действия:

type FooAction = { type: "FOO", payload: boolean };
type BarAction = { type: "BAR", payload: string };

type Action = FooAction | BarAction;

Передайте карту объекта js в createReducer, чтобы обеспечить функцию сокращения для каждого действия.

createReducer<Action>({}, {
  FOO: (state, action) => state,
  BAR: (state, action) => state,
})

Что я хотел бы видеть из системы типов:

  1. В функции редуктора action набрано правильно (FOO видит FooAction), что неправильноpayload генерирует ошибку.
  2. Ошибка, если карта содержит дополнительные (недействительные) ключи
  3. Ошибка, если на карте отсутствуют ключи (для определенных типов)

Часть, которую я не могу понять, состоит в том, могу ли я определить тип карты таким образом, чтобы ключ содержался в типе значения?

type Action<T = string> = { type: T, payload: any };
type Reducer<S, A> = (state: S, action: A) => S;
type HandlerMap<S, T> = {
  [T]: Reducer<S, Action<T>>,
};

Не думаю, что приведенное выше вполнеработает, потому что универсальный T будет содержать все строки типа, в отличие от ссылки на одну.

См. этот пример на try flow .Я чувствую, что это довольно близко, но я не могу полностью избавиться от всех ошибок типа.Любые волшебники потока, которые могут показать мне, как это делается?

1 Ответ

2 голосов
/ 11 марта 2019

Хотя проблема, точно такая, как указано, не имеет (я считаю) решения в текущую версию Flow, я могу предоставить подход, который работает, если Вы готовы предоставить Flow дополнительную информацию заранее.

Часть, которую я не могу понять, это то, могу ли я определить тип карты такой, что ключ должен содержаться в типе значения?

Для подобных задач $ObjMap обычно инструмент, к которому вы хотите обратиться. Но $ObjMap только трансформирует существующие типы карт, и он только преобразует их значения (независимо от ключа), поэтому здесь не совсем достаточно. Хитрость заключается в том, чтобы дать Flow отображение из имя действия для типа действия явно:

type Actions /* helper type, never instantiated */ = {
  FOO: FooAction,
  BAR: BarAction,
};
type Action = $Values<Actions>; // = FooAction | BarAction;

Я полагаю, что это не слишком нежелательно, так как вы уже определяете type Action в вашем примере. Учитывая, что вы можете определить Action в условия Actions, дополнительная избыточность невелика (по модулю обсуждение ниже о связывании ключей с type атрибутами - читайте дальше).

Учитывая такой тип карты действий, мы можем набрать createReducer следующим образом:

type HandlerMap<O, S> = $Exact<$ObjMap<O, <A>(A) => (S, A) => S>>;
type Reducer<O, S> = (S, $Values<O>) => S;

declare var createReducer: <O, S>(
  initialState: S,
  handlers: HandlerMap<O, S>
) => Reducer<O, S>;

Вот пример использования:

type FooAction = { type: "FOO", payload: number };
type BarAction = { type: "BAR", payload: string };

type Actions /* helper type, never instantiated */ = {
  FOO: FooAction,
  BAR: BarAction,
};

const reducer = createReducer<Actions, $ReadOnlyArray<string>>(
  ([]: $ReadOnlyArray<string>),
  {
    FOO: (state, action) => [...state, action.payload.toFixed(0)],
    BAR: (state, action) => [...state, action.payload],
    // Flow will flag a type error if you uncomment the following line,
    // complete with the helpful message "property `BAZ` is missing in
    // object type but exists in object literal".
    //BAZ: (state, action) => state,
  }
);

let result: $ReadOnlyArray<string> = [];
result = reducer(result, { type: "FOO", payload: 6 });
result = reducer(result, { type: "BAR", payload: "six" });
// Each of the following lines flags a type error if uncommented.
//result = reducer(result, { type: "FOO", payload: "six" });
//result = reducer(result, { type: "BAR", payload: 6 });
//result = reducer(result, { type: "BAZ", payload: true });

Обратите внимание, что ключи типа Actions не должны быть связаны в любой путь к type полям самих действий. Например, если вместо этого мы использовали определения типа действия

type FooAction = { type: "ONE", payload: number };
type BarAction = { type: "TWO", payload: string };

type Actions /* helper type, never instantiated */ = {
  FOO: FooAction,
  BAR: BarAction,
};

тогда код все еще будет успешно проверяться, но, вероятно, сбой во время выполнения (или, с другой точки зрения: невозможно на самом деле реализовать createReducer с вышеуказанными типами). Но не беспокойся, потому что мы можем попросить Flow проверить и это условие:

// Check that each the `type` property of each value in the action-map
// corresponds to that action's key.
(function<K: $Keys<Actions>>(
  x: $PropertyType<$ElementType<Actions, K>, "type">
): K {
  return x;
});

Выше не удастся проверить, если вы случайно определите действие у которого нет свойства type или чье свойство type не имеет соответствуют его ключу в типе Actions. Существование этого Функция является доказательством правильности построения карты. *

В идеале, мы могли бы потребовать, чтобы это условие выполнялось для любого карта действий O, которая используется с createReducer - мы должны иметь возможность написать

type HandlerMapCorrectnessProof<O> = <K: $Keys<O>>(
  $PropertyType<$ElementType<Actions, K>, "type">
) => K;

declare var createReducer: <O, S>(
  initialState: S,
  handlers: HandlerMap<O, S>,
  proof: HandlerMapCorrectnessProof<O>
) => Reducer<O, S>;

и просто требуют, чтобы клиенты передавали (x) => x в качестве третьего аргумента createReducer. Однако, если мы попытаемся сделать это, мы столкнемся с Flow проблема № 7548 , так что сейчас вам придется полагаться на внешнее проверка соответствия ваших карт действий (либо вручную) осмотр или с помощью отдельных статических проверок, как описано выше).

Итак, вот полный пример, включающий статические проверить .

Слово предупреждения: механизм, встроенный в этот ответ, сильно зависит от манипулирование на уровне типа, которое значительно сложнее, чем большинство Ежедневный код потока. Это имеет два важных значения: (а) это далеко более вероятно попадание в странные баги в потоке (как # 7548 выше), и (б) это может быть трудно понять для сотрудников, которые не очень хорошо разбираются в Поток или не привык думать о типах таким образом. Лично я нахожу что я получаю максимум от Flow, когда использую очень простое его подмножество, в основном просто непересекающиеся объединения точных объектов только для чтения . я представить этот пост в ответ на прямую просьбу ответить на этот вопрос, но я бы не решился сам его запустить в производство. Тот сказал, что это тема, по которой разумные люди могут разумно отличаться по мнению.

* Это утверждение не совсем точно, чтобы быть правильным - то, что я на самом деле это означает, что тот факт, что функция идентичности имеет этот тип является доказательством, или другими словами, что идентичность тип обитаем. Flow не имеет словарного запаса, чтобы правильно обсуждать типы идентичности, но его формулировка границ типов достаточно сильна, чтобы Я подозреваю, что любая функция этого типа, что текущая версия Поток принимает, фактически должен быть функцией идентификации.

...