
Содержание статьи
от Фабиана Терха

Эта статья разделена на 4 раздела:
- Проверка типа Redux действий, создание действий и редукторов
- Установка определений библиотеки Flow
- Проверка состояния программы
- Проверка типа 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% правильны. Если вы заметили ошибки или проблемы, пожалуйста, сообщите мне!