Сейчас много говорят о последнем мальчике в городе редуксе, Редукс Реду-Сага / Редукс-Сага . Он использует функции генератора для прослушивания / диспетчеризации действий.
Прежде чем обернуть голову, я хотел бы знать плюсы / минусы использования redux-saga
вместо подхода, описанного ниже, где я использую redux-thunk
с async / await.
Компонент может выглядеть так, диспетчеризировать действия как обычно.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
Тогда мои действия выглядят примерно так:
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
javascript
reactjs
redux
redux-thunk
redux-saga
hampusohlsson
источник
источник
::
как выthis.onClick
делаете?this
), иначеthis.onClick = this.onClick.bind(this)
. Более длинную форму обычно рекомендуется использовать в конструкторе, так как сокращение повторяет привязку при каждом рендере.bind()
много используют для переходаthis
к функции, но я начал использовать() => method()
сейчас.Ответы:
В redux-saga, эквивалент приведенного выше примера будет
Первое, на что нужно обратить внимание, это то, что мы вызываем функции API, используя форму
yield call(func, ...args)
.call
не выполняет эффект, он просто создает простой объект, такой как{type: 'CALL', func, args}
. Выполнение делегируется промежуточному программному обеспечению redux-saga, которое заботится о выполнении функции и возобновлении генератора с его результатом.Основным преимуществом является то, что вы можете протестировать генератор за пределами Redux, используя простые проверки на равенство
Обратите внимание, что мы высмеиваем результат вызова API, просто вставляя проверенные данные в
next
метод итератора. Дразнить данные намного проще, чем дразнить функции.Второе, на что стоит обратить внимание, это призыв к
yield take(ACTION)
. Thunks вызывается создателем действия при каждом новом действии (напримерLOGIN_REQUEST
). то есть действия постоянно толкнули на санки и санки не имеют никакого контроля о том, когда прекратить обработку этих действий.В Redx-сагах генераторы вытягивают следующее действие. то есть они имеют контроль, когда слушать какие-то действия, а когда нет. В приведенном выше примере инструкции потока размещены внутри
while(true)
цикла, поэтому он будет прослушивать каждое входящее действие, что несколько имитирует поведение толчка.Тяговый подход позволяет реализовать сложные потоки управления. Предположим, например, что мы хотим добавить следующие требования
Обработка действий пользователя LOGOUT
при первом успешном входе в систему сервер возвращает токен, срок действия которого истекает с некоторой задержкой, сохраненной в
expires_in
поле. Придется обновлять авторизацию в фоновом режиме каждыеexpires_in
миллисекунды.Примите во внимание, что при ожидании результата вызовов API (либо первоначального входа в систему, либо обновления) пользователь может выйти из промежуточного режима.
Как бы вы реализовали это с помощью Thunks; одновременно обеспечивая полное тестовое покрытие для всего потока? Вот как это может выглядеть с Sagas:
В приведенном выше примере мы выражаем наше требование параллелизма с помощью
race
. Еслиtake(LOGOUT)
победит в гонке (т.е. пользователь нажал на кнопку «Выйти»). Гонка автоматически отменитauthAndRefreshTokenOnExpiry
фоновое задание. И если онauthAndRefreshTokenOnExpiry
был заблокирован во времяcall(authorize, {token})
разговора, он также будет отменен. Отмена распространяется вниз автоматически.Вы можете найти работающую демонстрацию вышеупомянутого потока
источник
delay
функция? Ах, нашел его: github.com/yelouafi/redux-saga/blob/…redux-thunk
Код вполне читаем и самостоятельно объяснить. Ноredux-sagas
один действительно нечитаемым, в основном из - за этих глаголов-подобных функций:call
,fork
,take
,put
...Я добавлю свой опыт использования саги в производственную систему в дополнение к довольно полному ответу автора библиотеки.
Pro (используя сагу):
Тестируемость. Тестировать саги очень легко, так как call () возвращает чистый объект. Тестирование Thunks обычно требует, чтобы вы включили mockStore внутри вашего теста.
Redux-Saga поставляется с множеством полезных вспомогательных функций о задачах. Мне кажется, что идея саги заключается в создании своего рода фонового рабочего процесса / потока для вашего приложения, который выступает в качестве недостающего элемента в архитектуре реагирующего редукса (actionCreators и редукторы должны быть чистыми функциями.) Что приводит к следующему пункту.
Саги предлагают независимое место для обработки всех побочных эффектов. В моем опыте, как правило, легче модифицировать и управлять чем громоздкими действиями.
Против:
Синтаксис генератора.
Много идей для изучения.
API стабильность. Кажется, что Redx-сага все еще добавляет функции (например, каналы?), И сообщество не такое большое. Существует опасение, если библиотека когда-нибудь сделает несовместимое обновление.
источник
API stability
обновления, чтобы отразить текущую ситуацию.Я просто хотел бы добавить некоторые комментарии из моего личного опыта (с использованием саг и thunk):
Саги отлично подходят для тестирования:
Саги более могущественны. Все, что вы можете сделать в создателе действий одного толка, вы также можете сделать в одной саге, но не наоборот (или, по крайней мере, нелегко). Например:
take
)cancel
,takeLatest
,race
)take
,takeEvery
, ...)Sagas также предлагает другие полезные функции, которые обобщают некоторые общие шаблоны приложений:
channels
прослушивать внешние источники событий (например, веб-сокеты)fork
,spawn
)Саги отличный и мощный инструмент. Однако с властью приходит ответственность. Когда ваше приложение растет, вы можете легко потеряться, выяснив, кто ожидает отправки действия или что все происходит при отправке какого-либо действия. С другой стороны, это проще и легче рассуждать. Выбор того или другого зависит от многих аспектов, таких как тип и размер проекта, какие побочные эффекты должен обрабатывать ваш проект или предпочтения команды разработчиков. В любом случае просто сделайте ваше приложение простым и предсказуемым.
источник
Просто личный опыт:
Что касается стиля кодирования и читабельности, одно из наиболее значительных преимуществ использования redux-saga в прошлом - это избежать ада обратного вызова в redux-thunk - больше не нужно использовать много вложений then / catch. Но теперь, с популярностью асинхронного / ожидающего thunk, можно также написать асинхронный код в стиле синхронизации при использовании избыточного-thunk, что можно рассматривать как улучшение в избыточном мышлении.
Может потребоваться написать намного больше стандартного кода при использовании redux-saga, особенно в Typescript. Например, если кто-то хочет реализовать асинхронную функцию выборки, обработка данных и ошибок может быть выполнена непосредственно в одном модуле thank в action.js с одним единственным действием FETCH. Но в redux-saga, возможно, потребуется определить действия FETCH_START, FETCH_SUCCESS и FETCH_FAILURE и все связанные с ними проверки типов, потому что одна из функций в redux-saga - использовать этот богатый механизм «токенов» для создания эффектов и инструктирования Редукс магазин для легкого тестирования. Конечно, можно написать сагу, не используя эти действия, но это сделало бы ее похожей на Thunk.
С точки зрения структуры файлов, во многих случаях, как представляется, redux-saga более явный. Можно легко найти асинхронный код в каждом файле sagas.ts, но в избыточном коде его нужно будет увидеть в действиях.
Простое тестирование может быть еще одной взвешенной функцией в Redux-Saga. Это действительно удобно. Но одна вещь, которую необходимо пояснить, состоит в том, что тест «вызова» redux-saga не будет выполнять фактический вызов API в тестировании, поэтому необходимо будет указать пример результата для шагов, которые могут использовать его после вызова API. Поэтому, прежде чем писать в redux-saga, было бы лучше спланировать сагу и ее соответствующие sagas.spec.ts в деталях.
Redux-saga также предоставляет множество продвинутых функций, таких как параллельное выполнение задач, помощников по параллелизму, таких как takeLatest / takeEvery, fork / spawn, которые намного мощнее, чем thunks.
В заключение лично я хотел бы сказать: во многих нормальных случаях и приложениях от малого до среднего размера, используйте async / await style redux-thunk. Это сэкономит вам множество стандартных кодов / действий / typedefs, и вам не нужно будет переключаться между многими различными sagas.ts и поддерживать определенное дерево саг. Но если вы разрабатываете большое приложение с очень сложной асинхронной логикой и нуждаетесь в таких функциях, как параллелизм / параллельный шаблон, или если у вас высокий спрос на тестирование и обслуживание (особенно в разработке, управляемой тестированием), возможно, что при этом вы сможете сэкономить вашу жизнь. ,
В любом случае, redux-saga не более сложен и сложен, чем сам redux, и у него нет так называемой крутой кривой обучения, поскольку он имеет очень ограниченные основные концепции и API. Если вы потратите немного времени на изучение редукс-саги, это может принести вам пользу в будущем.
источник
Рассмотрев несколько различных крупномасштабных проектов React / Redux на своем опыте, Sagas предоставляет разработчикам более структурированный способ написания кода, который намного проще тестировать и труднее ошибиться.
Да, это немного странно, но большинство разработчиков достаточно понимают это за один день. Я всегда говорю людям не беспокоиться о том
yield
, с чего начать, и что, как только вы напишете пару тестов, это придет к вам.Я видел пару проектов, в которых thunks рассматривались так, как если бы они были контроллерами из паттерна MVC, и это быстро превращается в необратимый беспорядок.
Мой совет - использовать Sagas там, где вам нужны триггеры типа B, относящиеся к одному событию. Я считаю, что для всего, что может затрагивать несколько действий, проще написать клиентское промежуточное ПО и использовать мета-свойство действия FSA для его запуска.
источник
Thunks против саг
Redux-Thunk
иRedux-Saga
отличаются по нескольким важным аспектам, оба являются библиотеками промежуточного ПО для Redux (промежуточное ПО Redux - это код, который перехватывает действия, поступающие в хранилище с помощью метода dispatch ()).Действие может быть буквально любым, но если вы следуете передовым методам, это простой объект javascript с полем типа и необязательными полями полезной нагрузки, мета и ошибок. например
Redux-Thunk
Помимо отправки стандартных действий,
Redux-Thunk
промежуточное ПО позволяет отправлять специальные функции, называемыеthunks
.Thunks (в Redux) обычно имеют следующую структуру:
То есть a
thunk
- это функция, которая (необязательно) принимает некоторые параметры и возвращает другую функцию. Внутренняя функция принимает adispatch function
иgetState
функцию - обе они будут предоставленыRedux-Thunk
промежуточным программным обеспечением.Redux-Saga
Redux-Saga
Промежуточное программное обеспечение позволяет выражать сложную логику приложения в виде чистых функций, называемых сагами. Чистые функции желательны с точки зрения тестирования, потому что они предсказуемы и повторяемы, что делает их относительно легко тестировать.Саги реализуются через специальные функции, называемые функциями генератора. Это новая особенность
ES6 JavaScript
. По сути, выполнение включается и выходит из генератора везде, где вы видите оператор yield. Подумайте обyield
утверждении, которое заставляет генератор приостановить работу и вернуть полученное значение. Позже вызывающая сторона может возобновить работу генератора после оператора, следующего заyield
.Функция генератора определяется так. Обратите внимание на звездочку после ключевого слова функции.
Как только логин саги зарегистрирован
Redux-Saga
. Но затемyield
взятие первой строки приостановит сагу, пока действие с типом не'LOGIN_REQUEST'
будет отправлено в магазин. Как только это произойдет, выполнение будет продолжено.Для более подробной информации смотрите эту статью .
источник
Одна быстрая заметка. Генераторы отменяемы, async / await - нет. Так что для примера из вопроса, это действительно не имеет смысла, что выбрать. Но для более сложных потоков иногда нет лучшего решения, чем использование генераторов.
Таким образом, другая идея могла бы состоять в том, чтобы использовать генераторы с редуктором, но для меня это похоже на попытку изобрести велосипед с квадратными колесами.
И конечно, генераторы легче тестировать.
источник
Вот проект , который сочетает в себе лучшие части (профи) как
redux-saga
иredux-thunk
вы можете обрабатывать все побочные эффекты на сагах, получая обещание отdispatching
соответствующего действия: https://github.com/diegohaz/redux-saga-thunkисточник
then()
внутри компонента React противоречит парадигме. Вы должны обрабатывать измененное состояние,componentDidUpdate
а не ждать разрешения обещания.componentDidlMount() { this.props.doSomething().then((detail) => { this.setState({isReady: true})} }
Более простой способ - использовать redux-auto .
из документа
Идея состоит в том, чтобы каждое действие было в определенном файле . совместное размещение вызова сервера в файле с функциями редуктора для «ожидающих», «выполненных» и «отклоненных». Это делает обработку обещаний очень легкой.
Он также автоматически присоединяет вспомогательный объект (называемый «асинхронный») к прототипу вашего состояния, позволяя вам отслеживать в вашем пользовательском интерфейсе запрошенные переходы.
источник