Thunk react что это

Разбираемся в redux-saga: От генераторов действий к сагам

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Любой redux разработчик расскажет вам, что одной из самых тяжелейших частей разработки приложений являются асинхронные вызовы — как вы будете обрабатывать реквесты, таймауты и другие коллбэки без усложнения redux действий(actions) и редьюсеров(reducers).

В этой статье я опишу несколько различных подходов к управлению асинхронностью в вашем приложении, начиная от простых подходов как redux-thunk, заканчивая более продвинутыми библиотеками вроде redux-saga.

Мы собираемся использовать React и Redux, поэтому будем полагать, что вы имеете хотя бы какое то представление о том как они работают.

Генераторы действий (Action creators)

Взаимодействие с API довольно частое требование в приложениях. Представьте, что нам необходимо показывать случайную картинку собаки, когда мы нажимаем на кнопку.

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

мы можем использовать Dog CEO API и что-то довольно простое вроде вызова fetch внутри генератора действия (action creator).

Нет ничего плохого в таком подходе. При прочих равных всегда лучше использовать более простой подход.

Однако, использование только Redux не дает нам достаточно гибкости. Ядро Redux это контейнер состояния (state container), который поддерживает только синхронные потоки данных.

На каждое действие, в хранилище (store) посылается объект, описывающий что произошло, затем вызывается редюсер (reducer) и состояние (state) сразу обновляется.

Но в случае асинхронного вызова, вам необходимо сначала дождаться ответа и затем уже, если не было ошибок, обновить состояние. А что если у вашего приложения есть некая сложная логика/workflow?

Для этого Redux использует промежуточные слои (middlewares). Промежуточный слой это кусок кода, который выполняется после отправки действия, но перед вызовом редюсера.
Промежуточные слои могут соединяться в цепочку вызовов для различной обработки действия (action), но на выходе обязательно должен быть простой объект (действие)

Для асинхронных операций, Redux предлагает использовать redux-thunk промежуточный слой.

Redux-thunk

Redux-thunk является стандартным путем выполнения асинхронных операций в Redux.
Для нашей цели, redux-thunk вводит понятие преобразователь(thunk), что является функцией, которая предоставляет отложенное выполнение, по необходимости.

Значение 3 сразу присваивается переменной x.

Однако, если у нас есть выражение наподобие

То суммирование выполняется не сразу, а только при вызове функции foo(). Это делает функцию foo преобразователем(thunk).

Redux-thunk позволяет генератору действия (action creator) отправлять функцию в дополнении к объекту, конвертируя таким образом генератор действия в преобразователь.

Ниже, мы перепишем предыдущий пример используя redux-thunk

На первый взгляд он не сильно отличается от предыдущей версии.

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Преимуществом использования redux-thunk является то, что компонент не знает, что выполняется асинхронное действие.

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

Таким образом, с помощью механизма промежуточных слоев, мы добавили неявный слой (a layer of indirection), который дал нам больше гибкости.

Поскольку redux-thunk передает в возвращаемые функции методы dispatch и getState из хранилища (store) как параметры, то вы можете отсылать другие действия и использовать состояние (state) для реализации дополнительной логики и workflow.

Но что если у нас есть что-то более сложное, чтобы быть выраженным с помощью преобразователя (thunk), без изменения react компонента. В этом случае мы можем попробовать использовать другую библиотеку промежуточных слоев (middleware library) и получить больше контроля.

Давайте посмотрим как заменить redux-thunk на библиотеку, что может дать нам больше контроля — redux-saga.

Redux-saga

Redux-saga это библиотека нацеленная делать сайд-эффекты проще и лучше путем работы с сагами.

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

Чтобы узнать больше о сагах можно начать с просмотра Применения паттерна Сага от Caitie McCaffrey, ну а если вы амбициозны, то здесь Статья, которая первая описывает саги в отношении распределенных систем.

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

Redux-saga делает это с помощью ES6 генераторов

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Генераторы (Generators) это функции которые могут быть остановлены и продолжены, вместо выполнения всех выражений в один проход.

Когда вы вызываете функцию-генератор, она возвращает объект-итератор. И с каждым вызовом метода итератора next() тело функции-генератора будет выполняться до следующего yield выражения и затем останавливаться.

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Это делает асинхронный код проще для написания и понимания.
Для примера вместо следующего выражения:

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

С генераторами мы бы написали так:

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Возвращаясь к redux-saga, если говорить в общем, мы имеем сагу чья работа это следить за отправленными действиями (dispatched actions).

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Для координирования логики, которую мы хотим реализовать внутри саги, мы можем использовать вспомогательную функцию takeEvery для создания новой саги для выполнения операции.

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Если есть несколько запросов, takeEvery стартует несколько экземпляров саги-рабочего (worker saga). Иными словами реализует конкурентность(concurrency) для вас.

Надо отметить, что сага-наблюдатель (watcher saga) является другим неявным слоем (layer of indirection), который дает больше гибкости для реализации сложной логики (но это может быть лишним для простых приложений).

Теперь мы можем реализовать fetchDogAsync() функцию (мы полагаем, что у нас есть доступ к методу dispatch)

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Но redux-saga позволяет нам получить объект, который декларирует наше намерение произвести операцию, вместо результата выполнения самой операции. Иными словами, пример выше реализуется в redux-saga следующим образом:

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

(Прим. переводчика: автор забыл заменить самый первый вызов dispatch)
Вместо вызова асинхронного реквеста напрямую, метод call вернет только объект описывающий эту операцию и redux-saga сможет позаботиться о вызове и возвращении результатов в функцию-генератор.

Тоже самое касается и метода put. Вместо отправления действий (dispatch action) внутри функции-генератора, put возвращает объект с инструкциями для промежуточного слоя (middleware) — отправить действие.

Эти возвращаемые объекты называются Эффекты (Effects). Ниже пример эффекта возвращаемого методом call:

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Работая с Эффектами, redux-saga делает саги скорее Декларативными, чем Императивными.

Декларативное программирование это стиль программирования, который пытается минимизировать или устранить сайд-эффекты, описанием что программа должна делать, вместо описания как она должна это делать.

Преимущество, которое это дает, и о чем говорят большинство людей, то что функцию, которая возвращает простой объект, гораздо проще тестировать, чем функцию, которая делает асинхронный вызов. Для тестирования, вам не нужно использовать реальное АПИ, делать фейки или мокать.

Для тестирования, вы просто итерируете функцию-генератор делая assert и сравниваете полученные значения.

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это
Еще одно дополнительное преимущество это возможность легко объединять разные эффекты в сложный workflow.

Возвращаясь к нашему простому примеру, ниже полная реализация в redux-saga:

Когда вы нажимаете на кнопку, вот что происходит:

1. Отправляется действие FETCHED_DOG
2. Сага-наблюдатель (watcher saga) watchFetchDog получает это действие и вызывает сагу-рабочего (worker saga) fetchDogAsync.
3. Отправляется действие по отображению индикатора загрузки.
4. Происходит вызов API метода.
5. Отправляется действие по обновлению состояния (успех или провал)

Если вы считаете, что несколько неявных слоев и чуть-чуть дополнительной работы стоят этого, то redux-saga может дать вам больше контроля для обработки сайд-эффектов функциональным способом.

Заключение

Эта статья показала как реализовать асинхронные операции в Redux с помощью генераторов действий (action creators), преобразователей (thunks), и саг (sagas), идя от простого подхода к более сложному.

Redux не предписывает решение для обработки сайд-эффектов. Когда вы будете решать какому подходу следовать, вам необходимо учитывать сложность вашего приложения. Моя рекомендация — начинать с простого решения.

Также есть альтернативы redux-saga, которые стоит попробовать. Две самых популярных это redux-observable (который базируется на RxJS) и redux-logic (также базирующийся на RxJS наблюдателях, но дающий свободу писать вашу логику в других стилях).

Источник

Зачем нужен redux-thunk. Где его использовать, а где можно обойтись и без него

Йо-йо. Так как я сейчас работаю frontend-разработчиком и пишу свой код на react’е мне приходится работать и с redux, чтобы хранить данные и как-то их изменять. Конечно же изменение этих данных влечёт новый рендер.

Из-за того, что данных бывает очень много и, порой, нужно сделать множество преобразований за одно действие, причём изменить данные или запросить их на сервере простого redux не хватает. Приходится пользоваться middleware redux-thunk. В этой статье я хочу поделиться своим опытом использования redux-thunk, рассказать где без него не обойтись, а где для лучшего UX его миновать.

Что такое middleware

Это какая-то функция, которая берёт текущий store, текущий action и что-то делает с этим. Более подробно лучше прочитать в документации.

Redux-thunk — это как раз такая функция, которая, что-то делает со store.

Зачем использовать redux-thunk

Если вы задались этим вопросом, значит вы уже пользуетесь redux и знаете, что обычно action является простым объектом, который используется в reduser, a action creator имеет примерно такой вид:

В данном примере данные будут переданы в переменной array, эти данные и получит reduser и передаст в store.

Пример 1

В примере выше всё очень просто, но часто мы ещё не имеем ни каких данных и их нужно запросить с сервера. Тут то нам и нужен middleware для того, чтобы вызвать dispatch в то время, когда данные уже будут получены. Тогда наш action creator примет такой вид:

В этой функции мы явно вызываем dispatсh, в тот момент, когда мы получим данные.

Пример 2

Кроме того, что мы не имеем данных, бывает, что нужно вызвать сразу несколько action’ов. Например вы хотите сделать ваш компонент неактивным, пока идёт запрос. Вы можете сделать несколько вызовов dispatch в одном экшене.

В данном примере в начале я вызываю другой action creator (setDetouchStadia), делаю запрос на сервер и в любом случает вызываю его заново.

Пример 3

Иногда за одно действие может быть вызвано огромное количество изменений. Сейчас (декабрь 2019) я работаю над плеером для музыки 8bit. Как у любого плеера у него есть пульт для управления треками и список треков, которые можно выбрать. В момент выбора трека мне нужно сделать множество действий, и знать кое-какие данные из store.

Как я уже говорил redux-thunk, как и другие middleware даёт нам доступ к store:

Переменная getState — это функция, которая возвращает текущий экземпляр store.

Алгоритм действий, которые я решил, чтобы выполнялся при выборе трека:

В итоге у меня получился вот такой action creator

Когда не нужно использовать redux-thunk

Как может показаться, каждый раз, когда мы используем запросы к серверу мы должны использовать его… А вот и нет.

Хороший пример у меня был совсем недавно в статье про getDerivedStateFromProps. Там говорилось об отмене подписке на уведомления. Там использовался хук реакта и state компонента для изменения состояния подписки и ожидание нового значения из store. Но можно пойти другим путём.

По сути нам нужно было изменить в redux данные об элемент, но если бы мы отправили запрос на сервер в action и ждали ответ сервера то казалось бы, что наш сайт тормозит. Следовательно нам нужно изменить данные и отослать запрос на сервер. И так

Как вы видите здесь не подразумевалось получение важных данных с сревера. По-этому лучше тут не использовать middleware если оно не нужно. Вполне возможно, что у вас есть только подобные случаи в приложении и не нужно увеличивать бандл без необходимости.

Ещё пример

Иногда нам нужно изменить данные опираясь на state и не нужно получать весь, store целиком. В этом случае тоже можно отказаться от thunk а за место него использовать функции в самом reduser’е. Как я показал выше.

Про react и redux стало на статью больше. Если вы не согласны с моим мнение то можете написать своё в комментариях. А если согласны то не постесняйтесь поддержать проект денежкой)

Поддержи Xakplant

Я давно хочу развить видеоверсию, но пока этого не получается из-за нехватки ресурсов. Сейчас я собираю деньги на новый компьютер и микрофон. Поддержи xaklant и ты увидишь полезные видео быстрее.

Источник

Русские Блоги

Перевод | Краткое руководство по Redux и Thunk в React

title: Перевод | Краткое руководство по Redux и Thunk в React
date: 2017-04-15 21:36:07
категории: перевод
tags: Redux

Как использовать Redux и Thunk для чайников

Если вы похожи на меня, прочтите документацию Redux, посмотрите видео Дэна, курс Уэса и все еще не можете понять суть того, как использовать Redux, надеюсь, этот глупый учебник может вам помочь.
Перед фактической реализацией я предпринял несколько попыток, поэтому решил написать шаг за шагом из существующего приложения, которое использует выборку данных JSON, в приложение, которое использует Redux и Redux Thunk. Не знаю, что такое Thunk, не волнуйтесь, мы будем использовать его для выполнения асинхронных вызовов функций в разделе «Road to Redux».
Это руководство требует, чтобы вы имели базовые знания React и ES6 / 2015.

Не-Redux метод

в components/ItemList.js Создайте компонент React для получения и отображения списка элементов.

Перечислите основные компоненты

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

Это может показаться не особо показательным, но это уже хорошее начало.
При рендеринге компонент выводит четыре списка элементов, но если вы поместите isLoading с участием hasError Когда состояние меняется с ложного на ложное, соответствующий

Он будет отображаться (обратите внимание, что каждый компонент возвращается только по одному за раз).

Изменить на динамическое значение

Преобразование на самом деле довольно простое.

После завершения код выглядит следующим образом

Однако в действительности компоненты не должны содержать определенной логики выборки, а данные не должны храниться в состоянии компонента, поэтому Redux появляется в реальном времени.

Перейти на Redux

Необходимо добавить Redux, React Redux и Redux Thunk в качестве зависимостей.

Понять Redux

Есть несколько основных принципов Redux, которые мы должны понять (Аннотация: слова очень простые, но для создания рабочего процесса Redux в мозгу требуется много времени и энергии).

Компоненты упаковывают все приложение и доставляют store К дочерним компонентам.

Дизайн нашего государства

Создавать действия

Создайте файл itmes.js в каталоге действий, который содержит наши создатели действий. Создайте три простых действия.

export function itemsHasErrored(state = false, action) <
switch (action.type) <
case ‘ITEMS_HAS_ERRORED’:
return action.hasErrored;
default:
return state;
>
>
export function itemsIsLoading(state = false, action) <
switch (action.type) <
case ‘ITEMS_IS_LOADING’:
return action.isLoading;
default:
return state;
>
>
export function items(state = [], action) <
switch (action.type) <
case ‘ITEMS_FETCH_DATA_SUCCESS’:
return action.items;
default:
return state;
>
>

import < items, itemsHasErrored, itemsIsLoading >from ‘./items’;
import < posts, postsHasErrored, postsIsLoading >from ‘./posts’;
export default combineReducers( <
items,
itemsHasErrored,
itemsIsLoading,
posts,
postsHasErrored,
postsIsLoading
>);

import < createStore, applyMiddleware >from ‘redux’;
import thunk from ‘redux-thunk’;
import rootReducer from ‘../reducers’;
export default function configureStore(initialState) <
return createStore(
rootReducer,
initialState,
applyMiddleware(thunk)
);
>

import React from ‘react’;
import < render >from ‘react-dom’;
import < Provider >from ‘react-redux’;
import configureStore from ‘./store/configureStore’;
import ItemList from ‘./components/ItemList’;
const store = configureStore(); // You can also pass in an initialState here
render(

import < connect >from ‘react-redux’;
import < itemsFetchData >from ‘../actions/items’;

const mapStateToProps = (state) => <
return <
items: state.items,
hasErrored: state.itemsHasErrored,
isLoading: state.itemsIsLoading
>;
>;

const mapDispatchToProps = (dispatch) => <
return <
fetchData: (url) => dispatch(itemsFetchData(url))
>;
>;

export default connect(mapStateToProps, mapDispatchToProps)(ItemList);

import React, < Component >from ‘react’;
import < connect >from ‘react-redux’;
import < itemsFetchData >from ‘../actions/items’;
class ItemList extends Component <
componentDidMount() <
this.props.fetchData(‘http://5826ed963900d612000138bd.mockapi.io/items’);
>
render() <
if (this.props.hasErrored) <
return

Sorry! There was an error loading the items

Источник

Зачем нужен redux-thunk?

Сегодня с коллегой возникла небольшая дискуссия, я утверждал, что redux-thunk очень удобная надстройка (middleware) над redux и позволяет создавать асинхронные экшены.
Коллега возразил, зачем нужен redux-thunk, когда можно сделать вот так:

Т.е он импортит store в определенный файл проекта и вызывает несколько экшенов

По началу я впал в ступор, действительно, зачем redux-thunk, если можно испортить стор, но позже проанализировав ситуацию, пришел к выводу, что асинхронные экшены, например как в примере выше загрузка профиля должна иметь 3 состояния:
— старт загрузки профиля (для отображения лоадера)
— данные профиля успешно загружены
— возникла ошибка при загрузке данных
исходя из этого, если действовать по методу коллеги, необходимо создавать для каждого действия отдельную функцию (экшены)

Вот пример с использованием redux-thunk

Преимущества redux-thunk очевидны, и больше всего мне нравится то, что с помощью redux-thunk всю логику можно хранить в экшенах и тем самым разгрузить компоненты, это одновременно делает проект чище, ведь логика будет находится внутри экшенов, вместо того, чтобы импортить стор в разные части проекта

Что вы скажите, какое мнение верно, и имеет ли права на жизнь предложенное коллегой решение?

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Если я правильно понял вашего коллегу, то речь была не о том, что нужно асинхронные функции через setTimeout вызывать, а это был просто пример, как сделать какой-то кусочек «типа асинхронным».
Его довод был в том, что вместо вызова dispatch из замыкания (полученного с помощью redux-thunk, например) он вызывал store.dispatch напрямую (то есть у объекта store, и этот store импортировал бы в каждом файле).

В остальном, все очень четко расписано в ответе Дэна Абрамова, который привел holymotion.
Если быть кратким, то вам нужна функция dispatch, ведь именно через нее вы «диспатчите» свои экшены. Вы не можете в асинхронном ответе написать:

Поэтому вы были бы обязаны в каждый action creator, который является асинхронным, передавать бы помимо нужных вам аргументов, еще и функцию dispatch из своего контейнера. Что не удобно. (это все есть по ссылке на stackoverflow, но более подробно)

Источник

Описание подхода к организации и тестированию кода с использованием Redux Thunk

В этой заметке я хотел бы поделиться своим подходом к организации и тестированию кода с использованием Redux Thunk в проекте на React.

Путь к нему был долог и тернист, поэтому постараюсь продемонстрировать ход мыслей и мотивацию, которые и привели к итоговому решению.

Описание приложения и постановка проблемы

Сначала немного контекста.

На рисунке ниже представлен макет типовой страницы в нашем проекте.

Thunk react что это. Смотреть фото Thunk react что это. Смотреть картинку Thunk react что это. Картинка про Thunk react что это. Фото Thunk react что это

Панели раскрываются при нажатии на соответствующие кнопки.

Данные по тому, какие вообще могут быть колонки в таблице, какие в них могут быть данные, как они должны отображаться, какие значения могут содержать фильтры и другая информация содержится в мета-данных таблицы, которая запрашивается отдельно от самих данных в начале загрузки страницы.

Получается, что текущее состояние таблицы и данных в ней зависит от трёх факторов:

Как было сказано выше, такая страница является типовой. Для каждой такой страницы (а если быть точнее, для таблицы в ней) заводится отдельная сущность в хранилище Redux для удобства оперирования её данными и параметрами.

Для того, чтобы можно было задавать однотипные наборы thunk-ов и action creator-ов и обновлять данные по конкретной сущности, используется следующий подход (своего рода фабрика):

Таким образом, данная фабрика позволяет гибко описать управление данными и параметрами конкретной сущности в хранилище Redux и связать это с соответствующей данной сущности таблицей.

1200 строк кода и тестировался с большим скрипом. Файл тестов также имел порядка 1200 строк, но при этом покрытие составляло в лучшем случае 40-50%.

Здесь пример, конечно, очень сильно упрощён, как по количеству thunk-ов, так и по их внутренней логике, но для демонстрации проблемы этого будет вполне достаточно.

Обратите внимание на 2 вида thunk-ов в примере выше:

Тестировалось всё это следующим образом (использовался фреймворк для тестирования Jest):

Попробуем. Для примера будем изменять только успешный сценарий.

Здесь метод jest.spyOn заменяет примерно (а может быть, и точно) такую реализацию:

Это позволяет нам «следить» за функцией и понимать, была ли она вызвана и с какими параметрами.

Получаем следующую ошибку:

но функция работает точно так же, как и до этого, то есть мок не сработал! Попробуем по-другому.

Получаем точно такую же ошибку. По какой-то причине наши моки не заменяют исходную реализацию функций.

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

Попытка решения проблемы №1. Я что-то слышал про Redux-Saga.

… и мне говорили, что тестирование сильно упрощается при использовании данного middleware.

«Почему бы тогда не попробовать этот middleware?» — подумал я и взялся за работу. Завёл техническую историю в Jira, создал в ней несколько задач (от исследования до реализации и описания архитектуры всей этой системы), получил «добро» и начал делать минимальную копию текущей системы с новым подходом.

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

Прошёл месяц или два, решения не находилось, и в какой-то момент, обсудив с тех. лидом (отсутствующее) продвижение по этой задаче, приняли решение отказаться от внедрения Redux-Saga в проект, поскольку к тому моменту это стало слишком дорого с точки зрения трудозатрат и возможного количества багов. Так мы окончательно остановились на использовании Redux Thunk.

Попытка решения проблемы №2. Thunk-модули

Была предложена следующая структура папок и файлов:

Пример с данной архитектурой находится здесь.

За модульность тестов пришлось заплатить дублированием кода и очень сильной зависимостью thunk-ов друг от друга. Малейшее изменение в цепочке вызовов приведёт к тяжёлому рефакторингу.

После возвращения из отпуска я с удивлением обнаружил, что мои изменения откатили. Оказалось, что появилась-таки страница, на которой может быть несколько независимых таблиц, то есть сделанное ранее допущение всё ломало. Так что вся работа была выполнена зря.

Ну почти. На самом деле можно было бы повторно сделать все те же действия (благо merge/pull request никуда не пропал, а остался в истории), оставив подход к архитектуре шаблонов неизменным, а изменив лишь подход к организации thunk-ов. Но такой подход всё же не внушал доверия из-за своей связанности и сложности. Возвращаться к нему не было никакого желания, хотя это и решало обозначенную проблему с тестированием. Нужно было придумать что-то другое, более простое и более надёжное.

Попытка решения проблемы №3. Кто ищет, тот найдёт

Здесь находится код с демонстрацией такого подхода.

Рассмотрим более детально каждый из появившихся и изменившихся файлов.

В принципе, в тестах можно было бы заменить последнюю проверку таким образом:

Тогда и мокать их с помощью jest.spyOn не было бы нужды. Это было сделано намеренно, чтобы продемонстрировать, как легко мокаются методы класса и как легко их при этом тестировать. Таким же образом можно замокать и другой thunk, если он является методом того же класса. Выглядит уже довольно прилично, но есть одна проблема.

Посоветовавшись с тех. лидом и не найдя очевидного решения, мы решили узнать у back-end разработчиков (пишущих на Java), как у них решается эта проблема. Насколько я понял, в Java/Spring инициализация сущностей производится в несколько этапов, а не сразу. Почему бы тогда и нам не попробовать сделать что-то подобное?

В итоге пришли к такому решению:

Данный подход работает потому, что во время создания экземпляра класса методу applyFilter нет необходимости знать, что есть какое-то поле this.dependencies и чему оно равно. Это необходимо знать только в момент вызова этого метода, а к этому моменту данное поле уже будет определено.

Если появится необходимость переопределять некоторые из методов, которые возвращает фабрика, то при описанном выше подходе понадобится сделать всего лишь пару простых шагов:

Таким образом, мы решили следующие проблемы, применив данный подход:

Бонус

В итоге пришли к такому решению:

На этом всё, спасибо за внимание!
Надеюсь, это поможет вам улучшить организацию и покрытие вашего кода при работе с Redux Thunk.

Пишите в комментариях, пользуетесь ли вы Redux Thunk и как тестируете описанные выше случаи. Будет интересно узнать, какие ещё подходы существуют.

Источник

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *