, если вас интересуют или интересуют акции США, вы можете добавить меня в WeChat: xiaobei060537, и общаться вместе 😝.
redux-saga — это промежуточное ПО для управления асинхронными операциями приложений Redux с аналогичными функциями.redux-thunk + async/await
, который хранит всю логику асинхронных операций в одном месте для централизованной обработки путем создания Sagas.
Эффекты редукс-саги
Эффекты в redux-saga — это простой текстовый объект JavaScript, который содержит некоторые инструкции, которые будут выполняться промежуточным программным обеспечением saga. Операции, выполняемые этими инструкциями, включают следующие три:
- Инициировать асинхронный вызов (например, запрос Ajax)
- Инициировать другие действия для обновления Магазина
- Призывайте другие саги
В Эффекты включено много инструкций, которые могут быть асинхронными.Справочник по APIпроверить
Особенности редукс-саги
- Удобно для тестирования, например:
assert.deepEqual(iterator.next().value, call(Api.fetch, '/products'))
- Действие может поддерживать свою чистоту, а асинхронные операции централизованы в саге для обработки.
- Рабочая форма watch/worker (прослушивание -> исполнение)
- реализован как генератор
- Хорошая поддержка сценариев приложений со сложной асинхронной логикой
- Асинхронная логика реализована более детально, что приводит к более четким процессам и более простому отслеживанию и устранению ошибок.
- Написание асинхронной логики синхронным способом больше соответствует логике человеческого мышления.
От redux-thunk к redux-saga
Предположим, что сейчас есть сценарий: пользователю необходимо проверить, соответствуют ли имя пользователя и пароль требованиям при входе в систему.
Реализовано с помощью redux-thunk
Логика получения пользовательских данных (user.js):
// 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 });
}
}
Логика проверки входа (login.js):
import request from 'axios';
import { loadUserData } from './user';
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 });
}
}
redux-saga
Всю асинхронную логику можно написать в saga.js:
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST) //等待 Store 上指定的 action LOGIN_REQUEST
try {
let { data } = yield call(loginRequest, { user, pass }); //阻塞,请求后台数据
yield fork(loadUserData, data.uid); //非阻塞执行loadUserData
yield put({ type: LOGIN_SUCCESS, data }); //发起一个action,类似于dispatch
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(userRequest, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
Трудности чтения
Для redux-saga все еще есть много вещей, которые трудно понять и скрыть.Следующий автор систематизирует концепции, которые я считаю более запутанными:
использование взятия
И take, и takeEvery прослушивают действие, но их роли несовместимы: TakeEvery отвечает каждый раз, когда запускается действие, а take отвечает, когда поток выполнения достигает инструкции take. takeEvery просто слушает действие и выполняет соответствующую функцию-обработчик. У него нет большого контроля над тем, когда выполняется действие и как реагировать на действие. Вызванная задача не имеет контроля над тем, когда она вызывается, и у них есть нет контроля над тем, когда прекратить прослушивание.Его можно вызывать только снова и снова каждый раз, когда сопоставляется действие. Но take может решить в функции-генераторе, когда реагировать на действие и что делать дальше.
Например, чтобы выполнить операцию ведения журнала при прослушивании всех типов триггеров действий, используйте takeEvery для достижения следующего:
import { takeEvery } from 'redux-saga'
function* watchAndLog(getState) {
yield* takeEvery('*', function* logger(action) {
//do some logger operation //在回调函数体内
})
}
Используйте take для достижения следующего:
import { take } from 'redux-saga/effects'
function* watchAndLog(getState) {
while(true) {
const action = yield take('*')
//do some logger operation //与 take 并行
})
}
вwhile(true)
Это означает, что после достижения последнего шага процесса (регистратора) запустите новую итерацию (процесс регистратора), ожидая нового произвольного действия.
блокирующий и не блокирующий
Операция вызова используется для инициирования асинхронных операций.Для генераторов вызов является блокирующей операцией, которая не может выполнить или обработать что-либо еще, пока вызов генератора не завершится. , но fork — это неблокирующая операция.Когда fork мобилизует задачу, задача будет выполняться в фоновом режиме, и поток выполнения в это время может продолжать выполняться позже, не дожидаясь возврата результата.
Например, следующий сценарий входа:
function* loginFlow() {
while(true) {
const {user, password} = yield take('LOGIN_REQUEST')
const token = yield call(authorize, user, password)
if(token) {
yield call(Api.storeItem({token}))
yield take('LOGOUT')
yield call(Api.clearItem('token'))
}
}
}
Если результат не возвращается, когда вызов запрашивает авторизацию, но пользователь инициирует действие LOGOUT в это время, то LOGOUT в это время будет проигнорирован и не обработан, потому что loginFlow заблокирован при авторизации и не был выполнен.take('LOGOUT')
Там
Выполнять несколько задач одновременно
Если вы столкнулись со сценарием, который требует одновременного выполнения нескольких задач, таких как запрос данных о пользователях и данных о продуктах, вам следует использовать следующие методы:
import { call } from 'redux-saga/effects'
//同步执行
const [users, products] = yield [
call(fetch, '/users'),
call(fetch, '/products')
]
//而不是
//顺序执行
const users = yield call(fetch, '/users'),
products = yield call(fetch, '/products')
Когда за yield следует массив, операции в массиве будут следоватьPromise.all
Правила выполнения генератора блокируются до тех пор, пока не будут выполнены все эффекты.
Интерпретация исходного кода
В каждом проекте, использующем redux-saga, основной файл будет иметь следующую логику для добавления промежуточного программного обеспечения saga в Store:
const sagaMiddleware = createSagaMiddleware({sagaMonitor})
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga)
где createSagaMiddleware — это метод, экспортированный в основной исходный файл redux-saga src/middleware.js:
export default function sagaMiddlewareFactory({ context = {}, ...options } = {}) {
...
function sagaMiddleware({ getState, dispatch }) {
const channel = stdChannel()
channel.put = (options.emitter || identity)(channel.put)
sagaMiddleware.run = runSaga.bind(null, {
context,
channel,
dispatch,
getState,
sagaMonitor,
logger,
onError,
effectMiddlewares,
})
return next => action => {
if (sagaMonitor && sagaMonitor.actionDispatched) {
sagaMonitor.actionDispatched(action)
}
const result = next(action) // hit reducers
channel.put(action)
return result
}
}
...
}
Эта логика реализуется в основномsagaMiddleware()
, который назначает runSaga для sagaMiddleware.run, выполняет его и, наконец, возвращает промежуточное ПО. Затем посмотрите на логику runSaga():
export function runSaga(options, saga, ...args) {
...
const task = proc(
iterator,
channel,
wrapSagaDispatch(dispatch),
getState,
context,
{ sagaMonitor, logger, onError, middleware },
effectId,
saga.name,
)
if (sagaMonitor) {
sagaMonitor.effectResolved(effectId, task)
}
return task
}
Эта функция определяет и возвращает объект задачи, задача генерируется proc, перейдите в proc.js:
export default function proc(
iterator,
stdChannel,
dispatch = noop,
getState = noop,
parentContext = {},
options = {},
parentEffectId = 0,
name = 'anonymous',
cont,
) {
...
const task = newTask(parentEffectId, name, iterator, cont)
const mainTask = { name, cancel: cancelMain, isRunning: true }
const taskQueue = forkQueue(name, mainTask, end)
...
next()
return task
function next(arg, isErr){
...
if (!result.done) {
digestEffect(result.value, parentEffectId, '', next)
}
...
}
}
где выполняется дайджестЭффектeffectTriggerd()
а такжеrunEffect()
, то есть для выполнения эффекта, в котором runEffect() определяет соответствующие функции для выполнения различных эффектов, и каждая функция эффекта реализована в proc.js.
В дополнение к некоторым основным методам, redux-saga также предоставляет ряд вспомогательных файлов, функция которых состоит в том, чтобы возвращать итератороподобный объект для последующего обхода и выполнения, который здесь подробно анализироваться не будет.