redux-saga怎么用

1,337 阅读4分钟

简介

redux-saga是一个redux中间件,用于处理redux的Effect副作用(异步获取数据,访问浏览器缓存等)。利用ES6的Generator函数,实现异步操作的同步编程,使业务逻辑结构更加清晰

image.png

常用API

Saga生成器

首先我们要创建一个saga中间件

import createSagaMiddleware from 'redux-saga'
//创建saga中间件
const sagaMiddleware = createSagaMiddleware() 

然后我们需要向redux注册中间件

import { createStore, applyMiddleware } from 'redux'
const store = createStore(
    rootReducer,
    initialState,
    applyMiddleware([sagaMiddleware])
  )

最后我们需要把saga运行起来,监听store dispatch出来的action

import rootSaga from './saga'
sagaMiddleware.run(rootSaga)

⚠️注意:运行saga中间件必须在注册之后

Saga辅助函数

  1. takeEvery:监听action,并执行相应的Generator函数(effect)如:
import { takeEvery } from 'redux-saga/effects'
import { ActionType } from './action'
function* login() {
    //网络请求
}
function* rootSaga() {
  const result = yield takeEvery(ActionType.LOGIN ,login)
}
  1. takeLatest(最常用):对takeEvery做了防抖操作,即很短时间内触发多个action,只响应最后一个action。如
import { takeLatest } from 'redux-saga/effects'
import { ActionType } from './action'
function* login() {
    //网络请求
}
function* rootSaga() {
  const result = yield takeLatest(ActionType.LOGIN ,login)
}
  1. throttle:对takeEvery做了节流操作,第一个参数表示延时时间(单位ms)。即第一个参数指定时间内触发多个请求,第一个请求正常发出,第二个请求进入buffer,后面的请求都不会响应。第一个请求响应完成后处理buffer中的请求。也就是说会有两个请求响应。如
import { throttle } from 'redux-saga/effects'
import { ActionType } from './action'
function* login() {
    //网络请求
}
function* rootSaga() {
  const result = yield throttle(200, ActionType.LOGIN ,login)
}
//200ms内的多个请求,第一个正常发起,第二个进入buffer
  1. take:以上三个api都是先定义action和任务的对应关系,在每个action到来时执行对应的任务。take则不同,在saga运行【sagaMiddleware.run(rootSaga)】的时候,代码阻塞在take处,直到对应的action被触发,才开始继续往下执行。且是单次执行,即下次再触发action时并不会触发
function* loginFlow() {
  //需要循环监听
  while (true) {
    yield take(ActionType.LOGIN, login)
    // ... perform the login logic
    yield take(ActionType.LOGOUT, logout)
    // ... perform the logout logic
  }
}

Effect构建器

  1. call:用于发起effect请求,返回一条描述异步函数调用信息,配合yield调用异步函数并返回函数结果
import { call } from 'redux-saga/effects'
function* fetchProducts() {
  const products = yield call(Api.fetch, '/products')
  // ...
}
  1. apply: 与call函数作用一样,调用方式同js的apply函数
yield apply(obj, obj.method, [arg1, arg2, ...])
  1. fork: 原理与call和apply一样,但是call和apply是会阻塞的Effect,fork不会阻塞当前执行内容,可以继续往下执行,且fork返回一个task对象,可以被取消
import { fork, call, take, put } from 'redux-saga/effects'
import Api from '...'

function* authorize(user, password) {
  try {
    const token = yield call(Api.authorize, user, password)
    yield put({type: 'LOGIN_SUCCESS', token})
  } catch(error) {
    yield put({type: 'LOGIN_ERROR', error})
  }
}

function* loginFlow() {
  while(true) {
    const {user, password} = yield take('LOGIN_REQUEST')
    yield fork(authorize, user, password);
    //如果此处用call执行任务,在authorize执行未返回之前,程序都会被阻塞在这里,因而无法监听到后面的LOGOUT action,会导致UI和应用状态不一致的问题
    yield take(['LOGOUT', 'LOGIN_ERROR'])
    yield call(Api.clearItem('token'))
  }
}
  1. put: 同dispatch函数功能一样,用于发布一个同步action
import { call, put } from 'redux-saga/effects'
function* fetchProducts() {
  const products = yield call(Api.fetch, '/products')
  // 创建并 yield 一个 dispatch Effect
  yield put({ type: 'PRODUCTS_RECEIVED', products })
}
  1. select:用于获取store中的数据。当用户dispatch action时,saga先是透传数据给store reducer,然后在通知saga effect执行。如:
function* login() {
  const user = yield select((state) => state.login.user)
  try {
    const res = yield call(API.login, user)
    const data = yield res.json()
    //执行完异步网络请求,派发action
    yield put(Actions.login(data))
  } catch (error) {}
}

function* rootSaga() {
  //saga中间件拦截同步action
  yield all([takeLatest(ActionType.LOGIN, login)])
}

Effect组合器

  1. all:并行地运行多个 Effect,并等待它们全部完成。与Promise.all类似。如:
function* rootSaga() {
  //saga中间件拦截同步action
  yield all([takeLatest(ActionType.LOGIN, login)])
}
  1. race: 与Promise的race一样,用于创建多个effect之间的竞赛运行。常用于对一些接口访问进行timeout限制
import { race, take, call } from 'redux-saga/effects'

function* backgroundTask() {
  while (true) { ... }
}

function* watchStartBackgroundTask() {
  while (true) {
    yield take('START_BACKGROUND_TASK')
    yield race({
      task: call(backgroundTask),
      cancel: take('CANCEL_TASK')
    })
  }
}

基本用法

在介绍Saga生成器部分,我们已经介绍了基本用法,这里直接上代码吧

安装

$ yarn add redux react-redux redux-saga redux-logger redux-devtools-extension

创建sagas

// src/store/sagas/login
import { call, put, takeEvery } from 'redux-saga/effects'

function* fetchData(action) {
   try {
      const data = yield call(Api.fetchUser, action.payload.url);
      yield put({type: "FETCH_SUCCEEDED", data});
   } catch (error) {
      yield put({type: "FETCH_FAILED", error});
   }
   //此处的put相当于dispatch,都是发布一个同步action
}

export default function* () {
  yield takeEvery('FETCH_REQUESTED', fetchData)
  //每次触发同步FETCH_REQUESTED action时,执行fetchData函数,且允许多个fetchData函数同步执行
}
// src/store/sagas/index
import watchFetchData from './saga';

export default function* rootSaga() {
  yield all([
    watchFetchData(),
    ...
  ])
}

注册sagas

// store/index
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducer.js'
import rootSaga from './saga'

const sagaMiddleware = createSagaMiddleware() //创建saga中间件

const bindMiddleware = (middleware) => {
  if (process.env.NODE_ENV === 'development') {
    const { composeWithDevTools } = require('redux-devtools-extension')
    const { logger } = require('redux-logger')
    middleware.push(logger)
    return composeWithDevTools(applyMiddleware(...middleware))
  }
  return applyMiddleware(...middleware)
}

export default (initialState = {}) => {
  const store = createStore(
    rootReducer,
    initialState,
    bindMiddleware([sagaMiddleware])
  )
  sagaMiddleware.run(rootSaga)
  return store
}

参考: redux-saga