-
Notifications
You must be signed in to change notification settings - Fork 1
Description
前言
在上篇文章中,我们讲解了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呢?
-
saga首先必须是一个
generator,也就是使用function *的语法来定义的,虽然它使用function的关键字,但和普通的函数完全不同。如果你对generator的概念不熟悉,建议先去看一下相关文档,因为generator是使用redux-saga所必不可少的。 -
一个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>
)下面对代码做一下解读:
- 上面我们在
component.js中定义了一个Button组件,当点击Button被点击的时候,我们会调用普通的store.dispatch,然后传入一个普通的action object(如果是redux-thunk这里需要dispatch一个function才行) - 由于我们在mySaga中使用了
takeEvery,相当于注册了一个监听器,所有BOOK_FETCH_REQUESTED类型的action都会被劫持。然后redux-saga会调用我们传入的fetchBooks函数(generator)。 - 在
fetchBooks中,我们使用yield call的方式发出函数调用,真正的API请求在Api.fetchBooks中,redux-saga会帮我们调用它。 - 请求的结果返回后,如果成功,我们会使用
yield put来dispatch一个普通的action:BOOK_FETCH_SUCCEEDED,同样的,如果失败,我们会dispatchBOOK_FETCH_FAILED。
这里注意,put就相当于直接调用store.dispatch,区别就是它不会直接调用dispatch,而是发起一个调用请求,真正的调用由middleware底层处理 BOOK_FETCH_SUCCEEDED或BOOK_FETCH_FAILED的action到底redux进行分发,但由于我们的saga并没有监听这两种类型的action,所以它们会根据正常的dispatch流程进入reducer。
我们需要在reducer中对这两种类型的action进行处理,将数据存入store中。
总结
在本篇文章中,我们介绍了redux-saga的使用方法,以及它的几个核心概念。在介绍的过程中,我们分析了它与redux-thunk的异同点。最后我们通过一段示例代码讲解了redux-saga代码的执行流程。
本篇文章的定位为入门教程,目的是使大家可以对redux-saga的使用有个初步的认识。redux-saga的高级特性其实还有很多,比如除了本篇文章中提到的takeEvery外,它还有takeLatest,takeLeading等各种各样的流程控制函数,甚至可以通过一些底层的函数来组装自己的流程控制方式。这些内容我们留待以后有机会再介绍。
写文章不易,如果这篇文章帮助到了你,请帮忙关注和转发~ 谢谢
欢迎扫码关注公众号<前端时光机>:
