Skip to content

redux-saga入门 #16

@fanchangyong

Description

@fanchangyong

前言

上篇文章中,我们讲解了redux-thunk的使用和原理,其中提到了redux-saga。文章发出后后台有些朋友留言想让我讲一下redux-saga,那么我们今天就来简单介绍一下。
在Github上redux-saga的项目介绍是一句话:

An alternative side effect model for Redux apps

当中的alternative应该就是相对于redux-thunk说的,也就是说它和redux-thunk一样都是为了更好的管理redux中的side effect
redux-thunk不同,redux-saga是一个比较大的库,用到了ES6的generator等新的语言特性,而且它自己定义了比较多的概念,导致它的使用门槛是比redux-thunk要高不少的。这篇文章主要给大家讲解一下redux-saga的使用及核心概念,不太会涉及它的原理。
这篇文章假设你对redux以及redux middleware的使用比较熟悉,如果不熟可以先看这篇文章

开始使用

安装

要使用任何一个npm的库,第一步都是先安装,可以使用npm或者yarn:

> npm install --save redux-saga

或者:

yarn add redux-saga

注册middleware

redux-thunk一样,redux-saga也是一个redux的middleware。所以首先要使用applyMiddleware来注册middleware:

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import mySaga from './sagas'

// 创建saga middleware
const sagaMiddleware = createSagaMiddleware()
// 通过applyMiddleware将redux-saga注册到store中
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)
// 运行saga
sagaMiddleware.run(mySaga) 

redux-thunk相比,redux-saga多了一步调用.run的操作。
通过以上代码,即完成redux-saga的注册和启动工作。接下来主要是saga的编写,也就是处理具体业务逻辑的地方了。

核心概念

saga

看到saga这个词我们可能会感觉比较陌生,那么在redux-saga中到底什么是一个saga呢?

  1. saga首先必须是一个generator,也就是使用function *的语法来定义的,虽然它使用function的关键字,但和普通的函数完全不同。如果你对generator的概念不熟悉,建议先去看一下相关文档,因为generator是使用redux-saga所必不可少的。

  2. 一个saga可以启动其他的saga。

saga其实可以理解为就是一项任务,通常一项任务要分多个步骤完成,每个步骤我们都可以封装为一个小的独立的saga,然后调用不同的saga来完成特定的一项任务。

Effect

Effect就是一些普通的plain Object,这一点十分重要,因为它保证了redux-saga代码的可测试性。

我们可以通过redux-saga提供的一系列effect-creator函数来创建这些Effect,这些函数包括:put,call,take等。然后在saga中用yield关键字来执行这些Effect。

Effect可理解为我们发出的想要执行某项操作的指令(比如调用函数、请求API等),注意仅仅是发出指令,而具体操作的执行是由redux-saga的底层来处理的。比如以下代码:

import { call } from 'redux-saga/effects'
function *fetchItems() {
  yield call(Api.fetchItems)
}

我们把想要调用的函数传给call,但这并不会导致我们的函数被直接调用。我们只是发出了想要调用这个函数的指令,而函数的执行是由redux-saga的middleware来处理的。
这样做主要是为了方便写unit test。

总结

一个Saga可以简单理解为是一个函数,我们通过将一项复杂的流程拆分成多个小的函数来组织代码。我们通过yield关键字来调用Effect从而达到执行具体操作的目的。

我们可以通过if,while等普通的流程控制方法来控制Effect的流程,也可以通过try/catch的方式来处理异常。

编写saga

接下来我们来看一个saga的实例代码,假设我们有一个按钮,点击按钮会发出API请求,API请求返回之后我们需要往redux store中存入数据。

注册middleware的部分可以参考前面代码,这里代码就省略了。以下代码为伪代码,主要表达概念,无法直接运行

saga.js

import { call, put, takeEvery } from 'redux-saga/effects'
import Api from '...'

// 用于API请求的saga
function* fetchBooks() {
   // 使用普通的try/catch来处理函数
   try {
      // 调用call发起API请求
      const books = yield call(Api.fetchBooks);
      // 调用put将结果存入redux store
      yield put({type: "BOOK_FETCH_SUCCEEDED", books: books});
   } catch (e) {
   		// 错误信息,存入redux store
      yield put({type: "BOOK_FETCH_FAILED", message: e.message});
   }
}

// 入口saga
function* mySaga() {
  // 监听BOOK_FETCH_REQUESTED类型的action,如果监听到就发出API请求
  yield takeEvery("BOOK_FETCH_REQUESTED", fetchBooks);
}

export default mySaga;

component.js

// 按钮点击的处理函数
function handleClick() {
  // 这里就是正常的dispatch一个普通的plain object
  store.dispatch({ type: 'BOOK_FETCH_REQUESTED' })
}
const Button = (<button onClick={handleClick}>
Fetch Books
</button>
)

下面对代码做一下解读:

  1. 上面我们在component.js中定义了一个Button组件,当点击Button被点击的时候,我们会调用普通的store.dispatch,然后传入一个普通的action object(如果是redux-thunk这里需要dispatch一个function才行)
  2. 由于我们在mySaga中使用了takeEvery,相当于注册了一个监听器,所有BOOK_FETCH_REQUESTED类型的action都会被劫持。然后redux-saga会调用我们传入的fetchBooks函数(generator)。
  3. fetchBooks中,我们使用yield call的方式发出函数调用,真正的API请求在Api.fetchBooks中,redux-saga会帮我们调用它。
  4. 请求的结果返回后,如果成功,我们会使用yield put来dispatch一个普通的action: BOOK_FETCH_SUCCEEDED,同样的,如果失败,我们会dispatch BOOK_FETCH_FAILED
    这里注意,put就相当于直接调用store.dispatch,区别就是它不会直接调用dispatch,而是发起一个调用请求,真正的调用由middleware底层处理
  5. BOOK_FETCH_SUCCEEDEDBOOK_FETCH_FAILED的action到底redux进行分发,但由于我们的saga并没有监听这两种类型的action,所以它们会根据正常的dispatch流程进入reducer。
    我们需要在reducer中对这两种类型的action进行处理,将数据存入store中。

总结

在本篇文章中,我们介绍了redux-saga的使用方法,以及它的几个核心概念。在介绍的过程中,我们分析了它与redux-thunk的异同点。最后我们通过一段示例代码讲解了redux-saga代码的执行流程。
本篇文章的定位为入门教程,目的是使大家可以对redux-saga的使用有个初步的认识。redux-saga的高级特性其实还有很多,比如除了本篇文章中提到的takeEvery外,它还有takeLatesttakeLeading等各种各样的流程控制函数,甚至可以通过一些底层的函数来组装自己的流程控制方式。这些内容我们留待以后有机会再介绍。

写文章不易,如果这篇文章帮助到了你,请帮忙关注和转发~ 谢谢

欢迎扫码关注公众号<前端时光机>:

qrcode

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions