回顾
初识 react(一) 揭开 jsx 语法和虚拟 DOM 面纱
初识 react(二) 实现一个简版的 html+redux.JS 的 demo
初识 react(三)在 react 中使用 redux 来实现简版计数器
今天 demo 是实现一个异步的计算器, 探究 redux-saga 工作流程
简介
redux-saga 是一个 redux 的中间件, 而中间件的作用是为 redux 提供额外的功能.
由于在 reducers 中的所有操作都是同步的并且是纯粹的, 即 reducer 都是纯函数, 纯函数是指一个函数的返回结果只依赖于它的参数, 并且在执行过程中不会对外部产生副作用, 即给它传什么, 就吐出什么.
但是在实际的应用开发中, 我们希望做一些异步的 (如 Ajax 请求) 且不纯粹的操作(如改变外部的状态), 这些在函数式编程范式中被称为 "副作用".
redux-saga 就是用来处理上述副作用 (异步任务) 的一个中间件. 它是一个接收事件, 并可能触发新事件的过程管理者, 为你的应用管理复杂的流程.
redux-saga 工作原理
对 generator 不了解的, 看下阮一峰 generator 讲解
sages 采用 Generator 函数来 yield Effects(包含指令的文本对象).
Generator 函数的作用是可以暂停执行, 再次执行的时候从上次暂停的地方继续执行
Effect 是一个简单的对象, 该对象包含了一些给 middleware 解释执行的信息.
你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect.
redux-saga 分类
worker saga 做左右的工作, 如调用 API, 进行异步请求, 获取异步封装结果
watcher saga 监听被 dispatch 的 actions, 当接受到 action 或者知道其被触发时, 调用 worker 执行任务
root saga 立即启动 saga 的唯一入口
基本介绍已经讲完了, 当做完一个 demo 后, 回头再看 redux-saga 官网 https://redux-saga-in-chinese.JS.org/ 或者上面讲解, 可能会有更深的体会
使用 redux-saga 实现一个异步计数器
由于目录结构跟上篇文章一样, 在这里就只把变动的部分单独抽离出来讲解
先回顾下初识 react(三)在 react 中使用 redux 来实现简版计数器
1, 修改 actions/counter.JS
增加一个异步记数的动作类型.
- import * as Types from "../action-types";
- let actions ={
- add(num){
- return{type:Types.INCREMENT,count:num}
- },
- minus(num){
- return{type:Types.DECREMENT,count:num}
- },
- // 增加了一个异步记数的类型, 用于在 counter.JS 中派发动作
- async(num){
- return {type:Types.ADD_ASYNC}
- }
- };
- export default actions;
2, 重点, 在 src 目录下增加 saga.JS 文件
- //takeEvery=>负责监听 put=>派发动作 call=>告诉 saga, 执行 delay, 并传入 1000 作为参数
- import {takeEvery,put,call} from "redux-saga/effects";
- import * as Types from "./store/action-types";
- const delay = ms=>new Promise((resolve,reject)=>{
- setTimeout(()=>{
- resolve()
- },ms)
- })
- //saga 分为三类 1,rootsaga 2, 监听 saga 3,worker 干活的 saga
- function* add() {
- yield call(delay,1000);
- // 就是指挥 saga 中间件向仓库派发动作
- yield put({type:Types.INCREMENT,count:10});
- }
- function* watchAdd() {
- // 监听派发给仓库的动作, 如果动作类型匹配的话, 会执行对应的监听生成器
- yield takeEvery(Types.ADD_ASYNC,add)
- }
- export default function* rootSaga() {
- yield watchAdd()
- }
还记得上面说的, rudux-saga 分类, root saga ->watcher saga -> worker saga. 在这段代码中将会体现, 代码我们从上往下看.
2.1 saga 工作流程(代码从下往上看)
默认导出了 rootSaga, 即 saga 的入口文件
watcher saga ->wactchAdd 负责监听派发的动作, 如果动作类型匹配, 执行对应的 worker saga
上面的 worker saga 指代的是 add 函数
2.2 redux-saga/effects 副作用
在实际的应用开发中, 我们希望做一些异步的 (如 Ajax 请求) 且不纯粹的操作(如改变外部的状态), 这些在函数式编程范式中被称为 "副作用".
takeEvery=>负责监听, 监听派发给仓库的动作, 如果动作类型匹配的话, 会执行对应的监听生成器 ->add
put=>派发动作, 可以理解为 dispatch
call=>告诉 saga, 执行 delay 函数, 并把参数传过去. 注意: delay 函数必须返回 promise
讲到这里, 流程就说完了, 接下来在 store 中执行 rootSaga
3, 在 store/index 引入 rootSaga
- import {createStore,applyMiddleware} from 'redux';
- import createSagaMiddleware from "redux-saga"; // 引入 redux-saga
- import rootSaga from "../saga" // 引入我们上面写好的 rootSaga
- import reducer from "./reducers"
- let sagaMiddleware =createSagaMiddleware(); // 执行得到 saga 中间件
- let store = createStore(reducer,applyMiddleware(sagaMiddleware)); // 使用中间件
- sagaMiddleware.run(rootSaga); // 开始执行 rootSaga
- export default store;
对于 redux 中间件没有讲解, 这部分内容涉及东西比较多, 也不太好理解, 写这个 react 系列目的是尽可能简单的让所有人理解, 想看所有的 redux 源码解析, 底部会留下所有总结的代码仓库.
4, 在 counter.JS 组件中派发这个异步动作
代码跟上篇文章一模一样, 只是增加了按钮实现异步操作
- import React, {Component} from 'react';
- import ReactDOM from 'react-dom';
- import {connect} from "react-redux";
- import actions from "../store/actions/counter"
- class Counter extends Component {
- render() {
- console.log(this.props);
- return(
- <div>
- <h1>{this.props.number}</h1>
- <button onClick={()=>{this.props.add(5)}}>+</button>
- <button onClick={()=>{this.props.minus(1)}}>-</button>
- // 增加异步操作
- <button onClick={()=>{this.props.async()}}>异步加 10</button>
- </div>
- )
- }
- }
- export default connect(state=>({
- ...state
- }),actions)(Counter)
终结, 看效果. 可以看出, 点击后等待 1s 才加 10. 那我们就可以在 delay 中增加 Ajax 异步请求的动作啦.
最后在梳理下整个过程
1, 组件中调用了 this.props.async(), 返回的 action 对象 =>{type:Types.ADD_ASYNC}会在 connect 方法中被派发
2,saga 中 takeEvery(Types.ADD_ASYNC,add), 监听到动作的类型后, 触发 worker saga =>add
3,worker saga 中先 yield call(delay,1000); 执行 delay 方法, 延时 1s
4,yield put({type:Types.INCREMENT,count:10}); 最后派发的还是 INCREMENT 的类型
5, 接着被 reducer 处理, 更新 state
来源: https://juejin.im/post/5ba1bb506fb9a05d2d0218a5