Сводка опыта использования Redux-saga (включая пример кода),
Оригинальный адрес этой статьи:исходный адрес
Пример кода этой статьи:Образец кода адреса, добро пожаловать звезда
В последнее время промежуточное ПО redux в проекте заменено с redux-thunk на redux-saga.Возьмите на заметку подытожить опыт использования redux-saga.Читая эту статью, нужно знать, что такое redux и что такое redux-saga. использование промежуточного программного обеспечения Redux? Если вы понимаете две вышеупомянутые концепции, вы можете продолжить чтение этой статьи.
- Недостатки побочных эффектов обработки redux-thunk
- redux-saga пишет hellosaga
- Технические детали использования redux-saga
- redux-saga реализует пример входа и списка
1. Недостатки обработки побочных эффектов redux-thunk
(1) Обработка побочных эффектов редукции
Поток данных в редуксе примерно такой:
Пользовательский интерфейс ————> действие (обычный) ————> редуктор————> состояние————> пользовательский интерфейс
Redux следует правилам функционального программирования. В приведенном выше потоке данных действие представляет собой простой объект, а редюсер — это чистая функция. Для операций, которые являются синхронными и не имеют побочных эффектов, указанный выше поток данных может управлять данными. Тем самым контролируя цель обновление слоя просмотра.
Но если есть побочные эффекты, типа асинхронных запросов ajax и т. д., то что делать?
Если есть функция побочного эффекта, то нам нужно сначала обработать функцию побочного эффекта, а затем сгенерировать исходный объект js. Как справиться с операциями с побочными эффектами, выберите использование промежуточного программного обеспечения для обработки побочных эффектов между выдачей действий и функциями обработки редукторов в редукции.
Поток данных после того, как Redux добавляет промежуточное ПО для обработки побочных эффектов, примерно выглядит следующим образом:
Пользовательский интерфейс --> действие (побочная функция) --> промежуточное программное обеспечение --> действие (обычный) --> редьюсер --> состояние --> пользовательский интерфейс
Добавьте обработку промежуточного программного обеспечения между действием с побочными эффектами и исходным действием.Из рисунка также видно, что роль промежуточного программного обеспечения такова:
Преобразуйте асинхронную операцию для создания исходного действия, чтобы функция редуктора могла обработать соответствующее действие, тем самым изменив состояние и обновив пользовательский интерфейс.
(2) редукционный преобразователь
В редукции преобразователь — это промежуточное программное обеспечение, предоставленное автором редукции, Реализация чрезвычайно проста, с более чем 10 строками кода:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
То, что делают эти строки кода, также очень просто. Определите тип действия. Если действие является функцией, вызовите эту функцию. Шаги для вызова:
action(dispatch, getState, extraArgument);
Выяснилось, что фактические параметры — это dispatch и getState, поэтому мы определяем действие как функцию thunk, а общие параметры — это dispatch и getState.
(3) Недостатки избыточного преобразования
Недостаток ханка тоже очевиден, преобразователь только выполняет функцию, и ему все равно, что находится в теле функции, то есть преобразователь использует Redux может принимать функции как действия, но внутренности функций могут быть разными. Например, следующее действие соответствует асинхронной операции получения списка продуктов:
export default ()=>(dispatch)=>{
fetch('/api/goodList',{ //fecth返回的是一个promise
method: 'get',
dataType: 'json',
}).then(function(json){
var json=JSON.parse(json);
if(json.msg==200){
dispatch({type:'init',data:json.data});
}
},function(error){
console.log(error);
});
};
Из этого действия с побочными эффектами видно, что внутри функция чрезвычайно сложна. Если вам нужно определить подобное действие для каждой асинхронной операции, очевидно, что это действие непросто поддерживать.
Причины, по которым действия нелегко поддерживать:
- Форма действия неоднородна
- То есть асинхронная операция слишком разбросана, разбросана по каждому действию
2. redux-saga пишет hellosaga
В отличие от redux-thunk, redux-saga — это генератор, управляющий выполнением, в redux-saga action — это исходный js-объект, а все асинхронные операции с побочными эффектами помещаются в функцию saga. Это не только унифицирует форму действия, но и обеспечивает централизованную обработку асинхронных операций.
redux-saga реализуется генератором, если генератор не поддерживается, его необходимо экранировать с помощью плагина babel-polyfill. Давайте продолжим и реализуем пример, выводящий hellosaga.
(1) Создайте файл helloSaga.js
export function * helloSaga() {
console.log('Hello Sagas!');
}
(2) Используйте промежуточное ПО redux-saga в redux
В main.js:
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { helloSaga } from './sagas'
const sagaMiddleware=createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
);
sagaMiddleware.run(helloSaga);
//会输出Hello, Sagas!
Как и другое промежуточное ПО, которое вызывает redux, если вы хотите использовать промежуточное ПО redux-saga, просто вызовите экземпляр createSagaMiddleware в applyMiddleware. Единственное отличие состоит в том, что для запуска генератора необходимо вызвать метод run.
3. Технические детали использования redux-saga
В дополнение к вышеупомянутым преимуществам унифицированного действия и централизованной обработки асинхронных операций, redux-saga использует декларативные эффекты и обеспечивает более тонкое управление потоком.
(1) Декларативный эффект
Самой большой особенностью redux-saga является предоставление декларативных эффектов, которые позволяют redux-saga отслеживать действия в виде примитивных объектов js и облегчать модульное тестирование.Давайте рассмотрим их один за другим.
- Во-первых, в redux-saga предоставляется ряд API-интерфейсов, таких как take, put, all, select и т. д. API-интерфейсы определяются как Effect в redux-saga. После выполнения этих эффектов при разрешении функции возвращается объект описания, а затем промежуточное ПО redux-saga возобновляет выполнение функции в генераторе в соответствии с объектом описания.
Сначала посмотрите на общий процесс redux-thunk:
action1 (побочная функция) — > мониторинг редукционных переходов — > выполнить соответствующий метод с побочными эффектами — > action2 (обычный объект)
Преобразование в action2 — это действие в виде примитивного js-объекта, а затем выполнение функции редьюсера обновит состояние в хранилище.
Общий процесс redux-saga выглядит следующим образом:
action1 (обычный объект) -> мониторинг redux-saga-> выполнить соответствующий метод Effect-> вернуть объект описания-> возобновить выполнение асинхронных функций и функций с побочными эффектами-> action2 (обычный объект)
Сравнивая redux-thunk, мы обнаруживаем, что исходное действие js-объекта отслеживается в redux-saga, и он не будет выполнять операции с побочными эффектами сразу, а сначала преобразует его в объект описания через метод Effect, а затем использует описание объекта в качестве идентификатора, а затем восстановить его.Выполнение побочных функций.
Используя функцию класса Effect, можно упростить модульное тестирование, и нам не нужно проверять возвращаемый результат функции побочного эффекта. Нам нужно только сравнить объект описания, возвращаемый после выполнения метода Effect, чтобы убедиться, что он совпадает с ожидаемым объектом описания.
Например, метод call является методом класса Effect:
import { call } from 'redux-saga/effects'
function* fetchProducts() {
const products = yield call(Api.fetch, '/products')
// ...
}
Например, в приведенном выше коде нам нужно проверить, соответствует ли результат, возвращаемый Api.fetch, ожидаемому, и вернуть объект описания, вызвав метод call. Этот объект описания содержит вызываемый метод и фактические параметры при выполнении метода.Мы считаем, что до тех пор, пока объект описания один и тот же, то есть пока вызываемый метод и фактические параметры при выполнении метода являются Так же мы считаем, что окончательный результат выполнения должен соответствовать ожиданиям, чтобы модульное тестирование можно было проводить удобно, и не было необходимости моделировать конкретный возвращаемый результат функции Api.fetch.
import { call } from 'redux-saga/effects'
import Api from '...'
const iterator = fetchProducts()
// expects a call instruction
assert.deepEqual(
iterator.next().value,
call(Api.fetch, '/products'),
"fetchProducts should yield an Effect call(Api.fetch, './products')"
)
(2) Конкретные методы, предоставляемые Effect
Давайте представим несколько часто используемых методов в Effect, от низкоуровневых API, таких как take, call(apply), fork, put, select и т. д., до высокоуровневых API, таких как takeEvery и takeLatest и т. д., чтобы чтобы углубить понимание редукции — Знания об использовании саги (этот раздел может быть отрывистым, и он будет проанализирован в сочетании с конкретными примерами в Главе 3. В этом разделе сначала дается предварительное понимание различных Эффектов).
Представлять:
import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects'
- take
Метод take используется для отслеживания действия и возвращает отслеживаемый объект действия. Например:
const loginAction = {
type:'login'
}
Отправка действия в компоненте пользовательского интерфейса:
dispatch(loginAction)
Использование в саге:
const action = yield take('login');
Вы можете отслеживать действие, переданное пользовательским интерфейсом промежуточному программному обеспечению.Возврат вышеприведенного метода взятия является исходным объектом dipath. После отслеживания действия входа в систему возвращается следующее действие:
{
type:'login'
}
- call(apply)
Методы call и apply аналогичны call и apply в js.В качестве примера возьмем метод call:
call(fn, ...args)
Метод вызова вызывает fn, параметр имеет значение args и возвращает объект описания. Однако функция fn, переданная здесь методом вызова, может быть обычной функцией или генератором. Метод вызова широко используется и реализован в Redux-saga с использованием обычных методов вызова, таких как асинхронные запросы.
yield call(fetch,'/userInfo',username)
- put
Как упоминалось ранее, в качестве промежуточного программного обеспечения используется redux-saga, а рабочий процесс выглядит следующим образом:
Пользовательский интерфейс ——> action1————> промежуточное ПО redux-saga————> action2————> редуктор..
Из рабочего процесса мы обнаружили, что после того, как redux-saga выполнит функцию побочного эффекта, она должна выполнить действие, а затем это действие отслеживается редюсером, чтобы достичь цели обновления состояния. Соответственно, пут здесь соответствует отправке в редуксе, рабочий процесс выглядит следующим образом:
Из рисунка видно, что когда redux-saga выполняет метод побочного эффекта для преобразования действия, метод эффекта put похож на исходную отправку редукса, оба могут выдавать действия, а выданные действия будут контролироваться редьюсером. Как использовать пут:
yield put({type:'login'})
- select
Метод put соответствует диспетчеризации в Redux, Точно так же, если мы хотим получить состояние в промежуточном программном обеспечении, нам нужно использовать select. Метод select соответствует getState в redux, пользователь получает состояние в хранилище, метод использования следующий:
const state= yield select()
- fork
Метод fork будет подробно описан в примерах главы 3. Позвольте мне сначала упомянуть его. Метод fork эквивалентен веб-работе. Метод fork не блокирует основной поток и очень полезен при неблокирующих вызовах.
- взятьКаждый и взятьПоследний
takeEvery и takeLatest используются для отслеживания соответствующих действий и выполнения соответствующих методов.Это высокоуровневые API, построенные на основе взятия и разветвления.Например, для отслеживания действия входа в систему можно использовать метод takeEvery:
takeEvery('login',loginFunc)
Когда takeEvery прослушивает действие входа в систему, будет выполнен метод loginFunc.Кроме того, takeEvery может отслеживать несколько одинаковых действий одновременно.
Метод takeLatest вызывается так же, как и takeEvery:
takeLatest('login',loginFunc)
В отличие от takeLatest, takeLatest будет отслеживать и выполнять самое последнее инициированное действие.
4. redux-saga реализует пример входа и списка
Далее давайте реализуем пример redux-saga.Существует страница входа.После успешного входа в систему отображается страница списка, и на странице списка вы можете
Чтобы нажать Выход, вернитесь на страницу входа. Окончательный эффект отображения примера выглядит следующим образом:
Функциональная блок-схема образца:
Затем мы следуем описанному выше процессу, чтобы шаг за шагом реализовать соответствующие функции.
(1) LoginPanel (целевая страница)
Функции целевой страницы включают
- Сохраняйте имя пользователя при вводе
- Сохраняйте пароль при вводе
- Нажмите «Войти», чтобы запросить подтверждение успешного входа в систему.
I) Сохраняйте имя пользователя и пароль при вводе
Функции, запускаемые, когда поле ввода имени пользователя и поле пароля находятся в состоянии изменения:
changeUsername:(e)=>{
dispatch({type:'CHANGE_USERNAME',value:e.target.value});
},
changePassword:(e)=>{
dispatch({type:'CHANGE_PASSWORD',value:e.target.value});
}
В конце функции будут отправлены два действия:CHANGE_USERNAME и CHANGE_PASSWORD.
Прослушайте эти два метода в файле saga.js и выполните функцию побочного эффекта Наконец, put отправляет преобразованное действие и вызывает его в функции редуктора:
function * watchUsername(){
while(true){
const action= yield take('CHANGE_USERNAME');
yield put({type:'change_username',
value:action.value});
}
}
function * watchPassword(){
while(true){
const action=yield take('CHANGE_PASSWORD');
yield put({type:'change_password',
value:action.value});
}
}
Наконец, действие, переданное методом put из redux-saga, получено в редюсере:change_username и change_password, затем обновите состояние.
II) Отслеживайте события входа в систему, чтобы определить, был ли вход успешным
Событие входа в систему, выдаваемое в пользовательском интерфейсе, выглядит следующим образом:
toLoginIn:(username,password)=>{
dispatch({type:'TO_LOGIN_IN',username,password});
}
Действие события входа: TO_LOGIN_IN Обработчик события входа:
while(true){
//监听登入事件
const action1=yield take('TO_LOGIN_IN');
const res=yield call(fetchSmart,'/login',{
method:'POST',
body:JSON.stringify({
username:action1.username,
password:action1.password
})
if(res){
put({type:'to_login_in'});
}
});
В приведенной выше функции обработки сначала отслеживайте исходное действие для извлечения переданного имени пользователя и пароля, а затем запрашивайте, успешно ли выполнен вход в систему.
(2) LoginSuccess (страница отображения списка успешных входов в систему)
Функции страницы после успешного входа включают в себя:
- Получить информацию о списке, отобразить информацию о списке
- Функция выхода из системы, нажмите, чтобы вернуться на страницу входа
I) Получить информацию о списке
import {delay} from 'redux-saga';
function * getList(){
try {
yield delay(3000);
const res = yield call(fetchSmart,'/list',{
method:'POST',
body:JSON.stringify({})
});
yield put({type:'update_list',list:res.data.activityList});
} catch(error) {
yield put({type:'update_list_error', error});
}
}
Чтобы продемонстрировать процесс запроса, мы имитируем локально и используем задержку функции инструмента redux-saga. Функция задержки эквивалентна задержке xx секунд. Поскольку в реальном запросе есть задержка, задержку можно использовать для локально имитировать задержку запроса в реальной сцене.
II) Функция выхода
const action2=yield take('TO_LOGIN_OUT');
yield put({type:'to_login_out'});
Подобно входу в систему, функция выхода принимает действие:TO_LOGIN_OUT из пользовательского интерфейса, а затем перенаправляет действие:to_login_out.
(3) Полный код для входа в систему, выхода из системы и отображения списка
function * getList(){
try {
yield delay(3000);
const res = yield call(fetchSmart,'/list',{
method:'POST',
body:JSON.stringify({})
});
yield put({type:'update_list',list:res.data.activityList});
} catch(error) {
yield put({type:'update_list_error', error});
}
}
function * watchIsLogin(){
while(true){
//监听登入事件
const action1=yield take('TO_LOGIN_IN');
const res=yield call(fetchSmart,'/login',{
method:'POST',
body:JSON.stringify({
username:action1.username,
password:action1.password
})
});
//根据返回的状态码判断登陆是否成功
if(res.status===10000){
yield put({type:'to_login_in'});
//登陆成功后获取首页的活动列表
yield call(getList);
}
//监听登出事件
const action2=yield take('TO_LOGIN_OUT');
yield put({type:'to_login_out'});
}
}
Определите, был ли вход успешным, запросив код состояния.После успешного входа вы можете пройти:
yield call(getList)
способ вызова функции getList для получения списка действий. На первый взгляд проблем нет, но учтите, что вызов метода call заблокирует основной поток, а именно:
-
Операторы после вызова метода не могут быть выполнены до конца вызова метода вызова
-
Если в call(getList) есть задержка, оператор const action2=yieldtake('TO_LOGIN_OUT') после call(getList) не может быть выполнен до тех пор, пока метод вызова не вернет результат
-
Операции выхода из системы во время задержки игнорируются.
Блок-схему можно использовать для более четкого анализа:
Конкретный эффект вызова метода call для блокировки основного потока показан на следующей анимации:
Пустой экран - это время ожидания запроса списка. В это время мы нажимаем кнопку выхода из системы и не можем ответить на функцию выхода из системы. После успешного запроса списка и отображения информации списка нажмите кнопку выхода, чтобы иметь соответствующую функцию выхода из системы. То есть метод call блокирует основной поток.
(4) Неблокирующий вызов
В главе 2 мы рассказали, что метод fork может быть похож на веб-работу, а метод fork не блокирует основной поток. Применительно к приведенному выше примеру мы можем:
yield call(getList)
изменить на:
yield fork(getList)
Результат отображается следующим образом:
Метод fork не будет блокировать основной поток, нажмите «Выход», когда экран пуст, вы можете немедленно ответить на функцию выхода из системы и вернуться на страницу входа.
5. Резюме
В приведенных выше главах мы можем обобщить все преимущества redux-saga как промежуточного программного обеспечения redux:
-
Форма унифицированного действия, в редукционной саге действие отправки из пользовательского интерфейса является исходным объектом.
-
Централизованная обработка логики с побочными эффектами, такими как асинхронность
-
Преобразовав функцию эффектов, можно упростить модульное тестирование.
-
Совершенный и строгий контроль процесса может более четко контролировать сложную логику.