Научит вас полностью использовать Redux-saga (включая пример кода)

внешний интерфейс JavaScript React.js Redux

Сводка опыта использования 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) Обработка побочных эффектов редукции

Поток данных в редуксе примерно такой:

Пользовательский интерфейс ————> действие (обычный) ————> редуктор————> состояние————> пользовательский интерфейс

default

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

Но если есть побочные эффекты, типа асинхронных запросов ajax и т. д., то что делать?

Если есть функция побочного эффекта, то нам нужно сначала обработать функцию побочного эффекта, а затем сгенерировать исходный объект js. Как справиться с операциями с побочными эффектами, выберите использование промежуточного программного обеспечения для обработки побочных эффектов между выдачей действий и функциями обработки редукторов в редукции.

Поток данных после того, как Redux добавляет промежуточное ПО для обработки побочных эффектов, примерно выглядит следующим образом:

Пользовательский интерфейс --> действие (побочная функция) --> промежуточное программное обеспечение --> действие (обычный) --> редьюсер --> состояние --> пользовательский интерфейс

default

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

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

(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 (обычный объект)

2

Преобразование в action2 — это действие в виде примитивного js-объекта, а затем выполнение функции редьюсера обновит состояние в хранилище.

Общий процесс redux-saga выглядит следующим образом:

action1 (обычный объект) -> мониторинг redux-saga-> выполнить соответствующий метод Effect-> вернуть объект описания-> возобновить выполнение асинхронных функций и функций с побочными эффектами-> action2 (обычный объект)

default

Сравнивая 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 выполнит функцию побочного эффекта, она должна выполнить действие, а затем это действие отслеживается редюсером, чтобы достичь цели обновления состояния. Соответственно, пут здесь соответствует отправке в редуксе, рабочий процесс выглядит следующим образом:

default

Из рисунка видно, что когда 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.Существует страница входа.После успешного входа в систему отображается страница списка, и на странице списка вы можете

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

login

Функциональная блок-схема образца:

default

Затем мы следуем описанному выше процессу, чтобы шаг за шагом реализовать соответствующие функции.

(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) не может быть выполнен до тех пор, пока метод вызова не вернет результат

  • Операции выхода из системы во время задержки игнорируются.

Блок-схему можно использовать для более четкого анализа:

default

Конкретный эффект вызова метода call для блокировки основного потока показан на следующей анимации:

login_1

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

(4) Неблокирующий вызов

В главе 2 мы рассказали, что метод fork может быть похож на веб-работу, а метод fork не блокирует основной поток. Применительно к приведенному выше примеру мы можем:

yield call(getList)

изменить на:

yield fork(getList)

Результат отображается следующим образом:

login_2

Метод fork не будет блокировать основной поток, нажмите «Выход», когда экран пуст, вы можете немедленно ответить на функцию выхода из системы и вернуться на страницу входа.

5. Резюме

В приведенных выше главах мы можем обобщить все преимущества redux-saga как промежуточного программного обеспечения redux:

  • Форма унифицированного действия, в редукционной саге действие отправки из пользовательского интерфейса является исходным объектом.

  • Централизованная обработка логики с побочными эффектами, такими как асинхронность

  • Преобразовав функцию эффектов, можно упростить модульное тестирование.

  • Совершенный и строгий контроль процесса может более четко контролировать сложную логику.