Основная идея Redux — строгий односторонний поток данных, который можно передать только черезdispatch(action)
Способ модификации магазина, процесс выглядит следующим образом:
view -> action -> reducer -> store
В реальном бизнесе часто бывает большое количество асинхронных сценариев, самый примитивный подход — использование React-компонентов.componentDidMount
При инициализации асинхронного потока передайтеcallback
илиpromise
способ вызоваdispatch(action)
, делая так положитьview
слой иmodel
Слои смешиваются друг с другом, связь серьезная, а последующее обслуживание очень сложно.
предыдущая статьяИнтерпретация принципа промежуточного программного обеспечения ReduxВы можете знать, что промежуточное программное обеспечение (middleware) было переписаноdispatch
метод, поэтому более гибкое управлениеdispatch
синхронизации, что очень эффективно для обработки асинхронных сценариев. Поэтому авторы Redux также рекомендуют использовать промежуточное ПО для обработки асинхронных потоков. Распространенное промежуточное ПО в сообществе:redux-thunk
,redux-promise
,redux-saga
,redux-observable
Ждать.
redux-thunk: простой и грубый
Как асинхронное промежуточное ПО, написанное самим автором Redux, принцип очень прост: сам Redux обрабатывает только простые синхронные действия объекта, но его можноredux-thunk
Обработчики перехвата типа function (функция)action
, через обратный вызов для управления общим триггеромaction
, чтобы достичь цели асинхронности. Его типичное использование выглядит следующим образом:
//constants 部分省略
//action creator
const createFetchDataAction = function(id) {
return function(dispatch, getState) {
dispatch({
type: FETCH_DATA_START,
payload: id
})
api.fetchData(id)
.then(response => {
dispatch({
type: FETCH_DATA_SUCCESS,
payload: response
})
})
.catch(error => {
dispatch({
type: FETCH_DATA_FAILED,
payload: error
})
})
}
}
//reducer
const reducer = function(oldState, action) {
switch(action.type) {
case FETCH_DATA_START :
// 处理 loading 等
case FETCH_DATA_SUCCESS :
// 更新 store 等处理
case FETCH_DATA_FAILED :
// 提示异常
}
}
можно увидеть с помощьюredux-thunk
назад,action creator
Возвращаемое действие может быть функцией, и сама функция в нужный моментdispatch
Подходящее нормальное действие. И в этом нет никакой магии,redux-thunk
Его основной исходный код выглядит следующим образом:
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
Если действие есть функция, тоdispatch
Метод передается в функцию и выполняется.redux-thunk
Он очень удобен в использовании и подходит для большинства сценариев, но недостатком является то, что слишком много шаблонных кодов, и его немного трудоемко писать.
redux-promise: выполнять обещания до конца
redux-thunk
после того, как обещание, возвращенное из API, разрешаетсяdispatch
в другое действие, затем прямо дать обещание как действиеdispatch
, пусть промежуточное программное обеспечение обрабатывает процесс разрешения, разве нельзя было бы написать меньше.then().catch()
такой код?redux-promise
Именно это решило проблему. То же самое для перехода к данным из бэкэнда, и его типичное использование:
const FETCH_DATA = 'FETCH_DATA'
//action creator
const getData = function(id) {
return {
type: FETCH_DATA,
payload: api.fetchData(id) // 直接将 promise 作为 payload
}
}
//reducer
const reducer = function(oldState, action) {
switch(action.type) {
case FETCH_DATA:
if (action.status === 'success') {
// 更新 store 等处理
} else {
// 提示异常
}
}
}
Таким образом, по сравнению сredux-thunk
Стиль письма сильно похудел. Его основной исходный код иredux-thunk
Аналогично, еслиaction
илиaction.payload
даPromise
Тип разрешает это, вызывая текущийaction
копию и установите полезную нагрузку в результат успеха/неудачи промиса.
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {// 判断是否是标准的 flux action
return isPromise(action)
? action.then(dispatch)
: next(action);
}
return isPromise(action.payload)
? action.payload.then(
result => dispatch({ ...action, payload: result }),
error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
}
)
: next(action);
};
}
Присмотритесь, и вы найдетеredux-promise
написаниеreducer
Когда действие получено, оно было разрешено, поэтому, если вы хотите его обработатьloading
В этом случае необходимо написать дополнительный код и добавить его к простому объекту, такому как действие.status
Атрибуты дадут людям ощущение неравномерности, что может заключаться в том, что в яйцах легко запутаться, если шаги слишком велики.
redux-thunk
а такжеredux-promise
Использование на самом деле похоже, оба запускают функцию/обещание и позволяют промежуточному программному обеспечению решать самоdispatch
Синхронизация действительно асинхронных данных, достаточная для большинства сценариев. Однако для сценариев с более сложными асинхронными ситуациями нам часто приходится писать много бизнес-кода, и после возврата асинхронного результата его может потребоваться соответствующим образом изменить.store
Таким образом, мы сталкиваемся с запутанной проблемой: бизнес-код помещается вaction
слой илиreducer
внутри? Например, администратор замораживает учетную запись пользователя и одновременно требует ее обновления.store
внутриAllUserList
а такжеPendingUserlist
, на данный момент есть два варианта:
- Активировать кнопку при нажатии кнопки
PEND_USER
Затем действие редуктора одновременно обновляется в соответствующем переключателе редуктора.AllUserList
а такжеPendingUserlist
- Срабатывает при нажатии кнопки
REFRESH_USER_LIST
а такжеREFRESH_PENDING_USER_LIST
Два действия, а затем обновить два места в редюсереstore
.
Вообще говоря, для пользователя более разумно инициировать действие действием, но это может быть повторно использовано в другом месте.REFRESH_USER_LIST
Там, где действие разделено и обновлено, повторное использование более удобно, но в это время необходимо идти на компромисс.
redux-saga: точный и элегантный
а такжеredux-saga
Он может очень хорошо решить эту проблему, он добавляет к исходному потоку данных Redux.saga
слоя (не обращайте внимания на это странное имя 😂), отслеживайте действия и получайте новые действия для работы с магазином, которые будут представлены далее.redux-observable
Опять же, основное использование можно резюмировать следующим образом:Acion in,action out
.
Для вопроса только что,redux-saga
записывается как:
//action creator
const refreshUserListAction = (id)=>({type:REFRESH_USER_LIST,id:pendedUser.id})
const refreshPendingUserListAction = (id)=>({type:REFRESH_PENGDING_USER_LIST,id:pendedUser.id})
//saga
function* refreshLists() {
const pendedUser = yield call(api.pendUser)
// 将同时触发(put)两个 action
yield put(refreshUserListAction())
yield put(refreshPendingUserListAction())
}
function* watchPendUser() {
while ( yield take(PEND_USER) ) {
yield call(refreshLists) // 监听 PEND_USER 的 action,并执行(call)refreshLists 方法
}
}
//reducer 省略
Таким образом, бизнес-логика очень понятна: два триггера запускаются одним «PEND_USER».REFRESH
действие и введитеreducer
. И отделить бизнес-кодaction
слой иreducer
уровень, который уменьшает связанность кода и очень полезен для последующего обслуживания и тестирования.
Для более сложной асинхронности, такой как условия гонки,redux-saga
Это даже лучше:
Раньше я использовал сторонний клиент Weibo и обнаружил ошибку: когда я нажимаю на первый Weibo A, он переходит на страницу комментариев A. Я не хочу слишком долго ждать из-за скорости сети и Я возвращаюсь на домашнюю страницу, затем щелкнул другой Weibo B и перешел на страницу комментариев B. В это время был возвращен предыдущий запрос списка комментариев A, поэтому комментарии A отображались на странице комментариев Weibo B.
Если система сделана с помощью react/redux, причина ошибки очевидна:action
по прибытииreducer
когда следуетaction
Он больше не нужен. Если вы используетеredux-thunk
/redux-promise
Есть два способа решить эту проблему:
- Определить, что находится в текущем хранилище, когда промис возвращается
id
и до начала обещанияid
Это то же самое:function fetchWeiboComment(id){ return (dispatch, getState) => { dispatch({type: 'FETCH_COMMENT_START', payload: id}); dispatch({type: 'SET_CURRENT_WEIBO', payload: id}); // 设置 store 里 currentWeibo 字段 return api.getComment(id) .then(response => response.json()) .then(json => { const { currentWeibo } = getState(); // 判断当前 store 里的 id 和 promise 开始前的 id 是否相同: (currentFriend === id) && dispatch({type: 'FETCH_COMMENT_DONE', playload: json}) }); } }
- Включите Weibo в действие
id
, судите об этом при обработке редуктораid
а такжеurl
внутреннийid
То же самое или нет, здесь нет кода.
Короче говоря, кода будет много, и если таких сценариев в проекте много, то поддерживать в итоге будет больнее. И с redux-saga вы можете справиться с этим следующим образом:
import { takeLatest } from `redux-saga`
function* fetchComment(action) {
const comment = yield call(api.getComment(action.payload.id))
dispatch({type: 'FETCH_COMMENT_DONE', payload: comment})
}
function* watchLastFetchWeiboComment() {
yield takeLatest('FETCH_COMMENT_START', fetchComment)
}
takeLatest
метод фильтрацииaction
, следующийFETCH_COMMENT_START
изaction
Отменить предыдущий, когда он прибудетFETCH_COMMENT_START
изaction
срабатывает, последний сетевой запрос (состояние ожидания), который не вернул результат в это время, будет отменен.
Кроме тогоredux-saga
Он также предоставляет больше методов для обработки таких сценариев, как блокировка и параллелизм асинхронных запросов.Redux-saga Китайская документация.
Поэтому, если в проекте много сложных асинхронных сценариев, очень подходит redux-saga.
использоватьredux-saga
может держатьaction
а такжеreducer
легко читается, логически ясен, принявGenerator
, который может легко обрабатывать многие асинхронные ситуации, в то время какredux-saga
Недостатком является то, что он добавит слойsaga
слой, увеличивающий сложность начала работы;Generator
Отладка кода функции также более сложна, чем обычные функции.
redux-observable: более элегантные операции
можно увидетьredux-saga
идеи и предыдущиеredux-thunk
Есть большая разница, он реактивный (Reactive Programming):
В вычислительной технике реактивное программирование — это парадигма программирования, ориентированная на поток данных и распространение изменений. Это означает, что статические или динамические потоки данных могут быть легко выражены на языках программирования, а связанная вычислительная модель автоматически распространяет изменяющиеся значения через поток данных.
Для слоя действий, отправной точки потока данных, ему нужно только запуститьFETCH_COMMENT_START
Поток событий может завершить обновление всех данных, не заботясь о последующей обработке изменений данных.
Говоря об отзывчивости, мы должны упомянутьRxJS
сейчас,RxJS
Это мощная библиотека реактивного программирования, которая обеспечивает мощные возможности композиции и управления потоками данных.RxJS
Идея «все течет» в книге может быть очень запутанной для пользователей, мало знакомых с функциональным программированием (FP), но она внезапно станет ясной после того, как они овладеют ею. существуетRxJS
, наблюдатель (Observer
) подписывается на наблюдаемый (Observable
), ниже — наблюдаемые и традиционныеPromise
,Generator
Контраст:
можно увидетьObservable
МогуасинхронныйвернутьнесколькоВ результате обеспечивается больший оперативный контроль над данными. а такжеredux-observable
основывается наRxJS
Реализовано промежуточное ПО, которое создает побочные эффекты, создавая и отменяя асинхронные действия.redux-observable
Слой, отвечающий за асинхронность, называетсяEpic
(Не обращайте внимания на странное название), Epic берет функцию, которая принимает поток действий в качестве параметра и возвращает поток действий.
Давайте рассмотрим простой пример:
//epic
const fetchWeiboCommentEpic = action$=>
action$.ofType(FETCH_COMMENT_START) //ofType 表示过滤type 为 FETCH_COMMENT_START 的 action
.switchMap(action=>//switchMap 的作用类似 saga 中的 takeLatest,新的 action 会将老的 action 取消掉
Observable.fromPromise(api.getComment(action.payload.id))// 将 promise 转化成 Observable
.map(comment=>({type: 'FETCH_COMMENT_DONE', payload: comment})) // 将返回的 Obsevable 映射(map)成一个普通 action
.catch(err=>({type: 'FETCH_COMMENT_ERROR', payload: err})) // 这里的 err 也是一个 Observable,被捕获并映射成了一个 action
)
Настроеноredux-observable
После того, как промежуточное ПО может слушатьFETCH_COMMENT_START
изaction
И асинхронно инициировать запрос и вернуть успех или неудачу с соответствующими даннымиaction
. Видно, что благодаряRxJS
мощный, такой какswitchMap
оператор,redux-observable
Может завершить сложный процесс управления данными с помощью короткого кода. мы все еще можемfetchWeiboCommentEpic
для добавления более сложных операций, например, при полученииFETCH_COMMENT_START
Задержка 500 мс для повторной отправки запроса и получения искусственно отмененного действияFETCH_COMMENT_FORCE_STOP
Когда запрос завершается (например, пользователь нажимает кнопку, чтобы отменить загрузку), после получения комментария Weibo он напомнит «обновить успешно»:
//epic
const fetchWeiboCommentEpic = action$=>
action$.ofType(FETCH_COMMENT_START)
.delay(500) // 延迟 500ms 再启动
.switchMap(action=>
Observable.fromPromise(api.getComment(action.payload.id))
.map(comment=>[
{type: 'FETCH_COMMENT_DONE', payload: comment},
{type: 'SET_NOTIFICATION', payload: comment} // 同时提醒 “刷新成功”
])
.catch(err=>({type: 'FETCH_COMMENT_ERROR', payload: err}))
.takeUntil(action$.ofType('FETCH_COMMENT_FORCE_STOP')) // 人为取消加载
)
Давайте рассмотрим другой сценарий: когда пользователь вводит текст в поле поиска, результат извлекается из серверной части в режиме реального времени и возвращает наиболее подходящее приглашение (аналогично тому, что отображается при поиске в Google). Пользовательский ввод продолжает срабатыватьUSER_TYPING
действие, продолжайте запрашивать серверную часть, на этот раз используйтеredux-thunk
С ним будет сложнее иметь дело, иredux-observable
Это можно сделать элегантно:
const replaceUrl=(query)=>({type:'REPLACE_URL',payload:query})
const receiveResults = results=>({type:'SHOW_RESULTS',payload:results})
const searchEpic = action$=>action$.ofType('USER_TYPING')
.debounce(500) // 这里做了 500ms 的防抖,500ms 内不停的触发打字的操作将不会发起请求,这样大大节约了性能
.map(action => action.payload.query) // 返回 action 里的 query 字段,接下来的函数收到参数便是 query 而不是 action 整个对象了
.filter(query => !!query) // 过滤掉 query 为空的情况
.switchMap(query =>
.takeUntil(action$.ofType('CLEARED_SEARCH_RESULTS'))
.mergeMap(() => Observable.merge( // 将两个 action 以 Observable 的形式 merge 起来
Observable.of(replaceUrl(`?q=${query}`)),
Observable.fromPromise(api.search(query))
.map(receiveResults)
))
);
Кроме тогоRxJS
также обеспечиваетWebSocketSubject
объекты, с которыми можно легко и элегантно обращатьсяwebsocket
Ждем сцены, здесь ее разворачивать не буду.redux-observable
который предоставилObservable
СравниватьGenerator
более гибкая, благодаря мощномуRxJS
,redux-observable
Он более мощен в асинхронной обработке, что, вероятно, является наиболее элегантным в настоящее время.redux
Асинхронное решение тоже. Однако недостаток также очевиден, то есть слишком сложно начать работу.RxJS
Основные понятия не так просты для студентов, не знакомых с реактивным программированием. но через этот контактRxJS
Также очень полезно расширить свой кругозор. Поэтому вы можете попробовать использовать его в небольших проектах со сложными асинхронными сценариями.redux-observable
, а стоимость обучения для всей команды должна учитываться в крупномасштабных совместных проектах с участием нескольких человек. В этом случае обычно используетсяredux-saga
будет более рентабельным. В настоящее время используется внутри страныredux-observable
Их не так много, и я надеюсь пообщаться с вами здесь больше.redux-observable
соответствующий практический опыт.
Суммировать
Redux
Он будет обрабатывать только синхронныеaction
, поэтому асинхронные сценарии должны полагаться на различные асинхронные промежуточные программы в сообществе. В этой статье рассказывается об использовании некоторых распространенных асинхронных схем. В реальных проектах вам необходимо учитывать множество факторов, чтобы выбрать асинхронную схему, которая подходит вашей команде.