Исчерпывающее руководство по проверке типов React, Redux и React-Redux с помощью Flow

1656596889 ischerpyvayushhee rukovodstvo po proverke tipov react redux i react redux s

от Фабиана Терха

LwOjDA5I0tNxHZPOuhTS9abq4Bc3FxMr1SJQ

Эта статья разделена на 4 раздела:

  1. Проверка типа Redux действий, создание действий и редукторов
  2. Установка определений библиотеки Flow
  3. Проверка состояния программы
  4. Проверка типа Redux store and dispatch

Хотя в первой главе есть куча пособий, которые являются чрезвычайно полезными, я нашел лишь мало статей о 3 и 4. После долгого поиска в Google, погружение в исходный код и методом проб и ошибок я решил добавить вместе с тем, что я узнал, и напишите этот учебник как единственное руководство для проверки типа вашего приложения React + Redux + React-Redux с помощью Flow.

1. Проверка типа Redux actions, action creators, and reducers

Действия

Действия Redux, по сути, являются обычными объектами Javascript с обязательным type собственность:

// This is an action{  type: 'INCREASE_COUNTER',  increment: 1}

Следуя наилучшим практикам, вы можете вместо этого определить и использовать константы типа действия. Если да, приведенный выше фрагмент, вероятно, будет выглядеть примерно так:

const INCREASE_COUNTER = 'INCREASE_COUNTER';
// This is an action{  type: INCREASE_COUNTER,  increment: 1}

Проверка типов проста (здесь мы имеем дело с обычным JavaScript):

type $action = {  type: 'INCREASE_COUNTER',  increment: number};

Заметьте, что вы не можете заменить буквальный строчный тип константой INCREASE_COUNTER. Это ограничение самого Flow.

Создатели действий

Поскольку конструкторы действий – это только функции, возвращающие действия, мы все еще имеем дело с обычным Javascript. Вот как может выглядеть конструктор действий с проверкой типа:

function increaseCounter(by: number): $action {  return {    type: INCREASE_COUNTER, // it's okay to use the constant here    increment: by  };}

Редукторы

Редукторы – это функции, которые обрабатывать действия. Они получают состояние и действие и возвращают новое состояние. На этом этапе важно подумать о том, как будет смотреться ваш штат (форма состояния). В этом очень простом примере форма состояния состоит только из одного ключа counter принимающий а number значение типа:

// State shape{  counter: <number>}

Итак, ваш редуктор может выглядеть так:

const initialState = { counter: 0 };
function counter(state = initialState, action) {  switch (action.type) {    case INCREASE_COUNTER:      return Object.assign({}, state, {        counter: action.increment + state.counter      });        default:      return state;  }};

Примечание. В этом конкретном примере Object.assign({}, state, { ... }) является лишним, поскольку хранилище состоит из одной пары ключ/значения. Я мог бы легко вернуть последний аргумент функции. Однако я включил полную реализацию для корректности.

Ввести состояние и редуктор достаточно просто. Вот набранный версия приведенного выше фрагмента:

type $state = {  +counter: number};
const initialState: $state = { counter: 0 };
function counter(  state: $state = initialState,  action: $action): $state {    switch (action.type) {    case INCREASE_COUNTER:      return Object.assign({}, state, {        counter: action.increment + state.counter      });        default:      return state;  }};

Установка определений библиотеки Flow

Определения библиотеки потоков (или libdefs) дают определение типов для модулей сторонних разработчиков. В этом случае мы используем React, Redux и React-Redux. Вместо того, чтобы вводить эти модули и их функции вручную, можно установить их определение типа с помощью flow-typed:

npm install -g flow-typed
// Automatically download and install all relevant libdefsflow-typed install
// Orflow-typed install <package>@<version> // e.g. redux@4.0.0

Библиотечные определения устанавливаются в flow-typed папку, позволяющую Flow работать из коробки без какой-либо дополнительной конфигурации (детали).

Проверка состояния программы

Ранее мы уже вводили состояние следующим образом:

type $state = {  +counter: number};

Хотя это работает для простого примера, как приведенный выше, он выходит из строя, когда ваше состояние становится значительно больше. Вам придется вручную редактировать type $state всякий раз, когда вы вводите новый редуктор или изменяете существующий. Вы также не хотели бы держать все ваши редукторы в одном файле. Вы хотите сделать рефакторинг ваших редукторов на отдельные модули и использовать Redux combineReducers функция.

Поскольку в центре внимания этой статьи проверка типа я предполагаю, что вы знакомы с программой React/Redux/React-Redux, а не при ее создании. combineReducers функция. Если нет, перейдите к руководству Redux, чтобы узнать все об этом.

Предположим, мы вводим новую пару действие/редуктор в отдельном модуле:

// playSong.js
export const PLAY_SONG = 'PLAY_SONG';
// Typing the actionexport type $playSongAction = {  type: 'PLAY_SONG',  song: ?string};
// Typing the action creatorexport function playSong(song: ?string): $playSongAction {  return {    type: PLAY_SONG,    song: song  };};
// Typing arg1 and the return value of the reducer [*1]export type $song = ?string;
// Typing the state [*1]export type $songState = {  +song: $song};
// [*1][*2]const initialValue: $song = null;
// Typing the reducer [*1][*3]function song(  state: $song = initialValue,  action: $playSongAction): $song {    switch (action.type) {    case PLAY_SONG:      return action.song;        default:      return state;  }};

[*1]: Если мы используем combineReducers функции, важно отметить, что ваш редуктор больше не должен возвращать состояние, а скорее значение ключа в состоянии. В этом отношении я думаю, что учебнику Redux чуть-чуть не хватает ясности, поскольку в нем это не указано явно, хотя это понятно из фрагментов кода примера.

[*2]: редукторы нельзя возвращать undefinedпоэтому мы должны удовлетвориться null.

[*3]: Поскольку редуктор больше не получает и не возвращает состояние в виде { song: string }а скорее значение к song ключ в объекте состояния, нам нужно изменить типы его первого аргумента и вернуть значение $songState к $song.

Мы модифицируем и реорганизуем increaseCounter так же:

// increaseCounter.js
export const INCREASE_COUNTER = 'INCREASE_COUNTER';
export type $increaseCounterAction = {  type: 'INCREASE_COUNTER',  increment: number};
export function increaseCounter(by: number): $action {  return {    type: INCREASE_COUNTER,    increment: by  };};
export type $counter = number;
export type $counterState = {  +counter: $counter};
const initialValue: $counter = 0;
function counter(  state: $counter = initialValue,  action: $increaseCounterAction): $counter {    switch (action.type) {    case INCREASE_COUNTER:      return action.increment + state;        default:      return state;  }};

Теперь у нас есть 2 пары действие/редуктор.

Мы можем создать новый State введите, чтобы сохранить тип нашего состояния программы:

export type State = $songState & $counterState;

Это тип пересечения потока и эквивалентный:

export type State = {  song: $song,  counter: $counter};

Если вы не хотите создавать $songState и $counterState только для использования в пересечении ввода состояния программы Stateэто тоже замечательно – перейдите ко второй реализации.

Проверка типа Redux store and dispatch

Я обнаружил, что Flow сообщал об ошибках в моих контейнерах (в контексте парадигмы контейнера/компонента).

Could not decide which case to select. Since case 3 [1] may work but if it doesn't case 6 [2] looks promising too. To fix add a type annotation to dispatch [3].

Это касалось моего mapDispatchToProps функция. Случаи 3 и 6 таковы:

// flow-typed/npm/react-redux_v5.x.x.js
// Case 3declare export function connect<  Com: ComponentType<*>,  A,  S: Object,  DP: Object,  SP: Object,  RSP: Object,  RDP: Object,  CP: $Diff<$Diff<ElementConfig<Com>;, RSP>, RDP>  >(  mapStateToProps: MapStateToProps<S, SP, RSP>,  mapDispatchToProps: MapDispatchToProps<A, DP, RDP>): (component: Com) => ComponentType<CP & SP & DP>;
// Case 6declare export function connect<  Com: ComponentType<*>,  S: Object,  SP: Object,  RSP: Object,  MDP: Object,  CP: $Diff<ElementConfig<Com>, RSP>  >(  mapStateToProps: MapStateToProps<S, SP, RSP>,  mapDispatchToPRops: MDP): (component: Com) => ComponentType<$Diff<CP, MDP> & SP>;

Я не знаю, почему возникает это заблуждение. Но, как намекает ошибка, введение dispatch исправляет это. И если мы печатаем dispatchмы могли бы также ввести store также

Я не мог найти много документации по этому аспекту ввода программы Redux/React-Redux. Я узнал, погружаясь в libdefs и глядя на исходный код других проектов (хотя и демонстрационного проекта). Если у вас есть какие-либо идеи, пожалуйста, сообщите мне, чтобы я мог обновить эту публикацию (с надлежащей ссылкой, конечно).

Тем временем я обнаружил, что это работает:

import type {  Store as ReduxStore,  Dispatch as ReduxDispatch} from 'redux';
// import any other variables and types you may need,// depending on how you organized your file structure.
// Reproduced from earlier onexport type State = {  song: $song,  counter: $counter};
export type Action =   | $playSongAction  | $increaseCounterAction
export type Store = ReduxStore<State, Action>;
export type Dispatch = ReduxDispatch<Action>;

Перейдя к модулям контейнера, вы можете перейти к вводу mapDispatchToProps следующим образом: const mapDispatchToProps = (dispatch: Dispatch) => { ... };

Подведению

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

Я не могу гарантировать, что указания в этой статье соответствуют лучшим практикам, концептуально обоснованы или даже на 100% правильны. Если вы заметили ошибки или проблемы, пожалуйста, сообщите мне!

Добавить комментарий

Ваш адрес email не будет опубликован.