导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1. redux-saga
  • 2. redux-saga工作原理
  • 3. redux-saga分类
  • 4. 构建项目
    • 4.1 初始化项目
  • 5.跑通saga
    • 5.1 src\index.tsx
    • 5.2 store\index.tsx
    • 5.3 store\reducer.tsx
    • 5.4 store\sagas.tsx
  • 6.异步计数器
    • 6.1 src\index.tsx
    • 6.2 store\index.tsx
    • 6.3 store\reducer.tsx
    • 6.4 store\sagas.tsx
    • 6.5 action-types.tsx
    • 6.6 store\actions.tsx
    • 6.7 components\Counter.tsx
  • 7. 声明式effects
    • 7.1 src\utils.tsx
    • 7.2 src\store\sagas.tsx
  • 8. 错误处理
    • 8.1 try catch
    • 8.2 错误标识
  • 9. take
  • 10. 登陆流程
    • 10.1 src\index.tsx
    • 10.2 action-types.tsx
    • 10.3 store\actions.tsx
    • 10.4 store\reducer.tsx
    • 10.5 src\store\sagas.tsx
    • 10.6 src\Api.tsx
    • 10.7 components\Login.tsx
  • 11. fork
    • 11.1 sagas.tsx
  • 12. 取消任务
    • 12.1 components\Login.tsx
    • 12.2 sagas.tsx
  • 13. race
    • 13.1 src\index.tsx
    • 13.2 src\store\actions.tsx
    • 13.3 action-types.tsx
    • 13.4 src\store\sagas.tsx
    • 13.5 Recorder.tsx

1. redux-saga #

  • redux-saga 是一个 redux 的中间件,而中间件的作用是为 redux 提供额外的功能。
  • 在 reducers 中的所有操作都是同步的并且是纯粹的,即 reducer 都是纯函数,纯函数是指一个函数的返回结果只依赖于它的参数,并且在执行过程中不会对外部产生副作用,即给它传什么,就吐出什么。
  • 但是在实际的应用开发中,我们希望做一些异步的(如Ajax请求)且不纯粹的操作(如改变外部的状态),这些在函数式编程范式中被称为“副作用”。

redux-saga 就是用来处理上述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。

2. redux-saga工作原理 #

  • sages 采用 Generator 函数来 yield Effects(包含指令的文本对象)
  • Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行
  • Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。
  • 你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。

3. redux-saga分类 #

  • worker saga 做实际的工作,如调用API,进行异步请求,获取异步封装结果
  • watcher saga 监听被dispatch的actions,当接受到action或者知道其被触发时,调用worker执行任务
  • root saga 立即启动saga的唯一入口

4. 构建项目 #

4.1 初始化项目 #

cnpm install create-react-app -g 
create-react-app zhufeng-saga-start --typescript
cd zhufeng-saga-start
cnpm i redux react-redux @types/react-redux redux-saga tape --save

5.跑通saga #

5.1 src\index.tsx #

import store from './store';
console.log(store);

5.2 store\index.tsx #

src\store\index.tsx

import { createStore, applyMiddleware } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
//首先我们引入 ./sagas 模块中的 Saga。然后使用 redux-saga 模块的 createSagaMiddleware 工厂函数来创建一个 Saga middleware
import { helloSaga } from './sagas';
let sagaMiddleware = createSagaMiddleware();
//运行 helloSaga 之前,我们必须使用 applyMiddleware 将 middleware 连接至 Store。然后使用 sagaMiddleware.run(helloSaga) 运行 Saga。
let store = applyMiddleware(sagaMiddleware)(createStore)(reducer);
sagaMiddleware.run(helloSaga);
export default store;

5.3 store\reducer.tsx #

src\store\reducer.tsx

import { AnyAction } from 'redux';
export interface CounterState { };
let initialState = {};
export default function (state: CounterState = initialState, action: AnyAction): CounterState {
    return state;
}

5.4 store\sagas.tsx #

src\store\sagas.tsx

export function* helloSaga() {
    console.log('Hello Saga!');
}

6.异步计数器 #

6.1 src\index.tsx #

src\index.tsx

import React from 'react'
import ReactDOM from 'react-dom';
import Counter from './components/Counter';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
    <Counter />
</Provider>, document.querySelector('#root'));

6.2 store\index.tsx #

src\store\index.tsx

import { createStore, applyMiddleware } from 'redux';
import reducer from './reducer';
import createSagaMiddleware from 'redux-saga';
//首先我们引入 ./sagas 模块中的 Saga。然后使用 redux-saga 模块的 createSagaMiddleware 工厂函数来创建一个 Saga middleware
+import rootSaga from './sagas';
let sagaMiddleware = createSagaMiddleware();
//运行 helloSaga 之前,我们必须使用 applyMiddleware 将 middleware 连接至 Store。然后使用 sagaMiddleware.run(helloSaga) 运行 Saga。
let store = applyMiddleware(sagaMiddleware)(createStore)(reducer);
+sagaMiddleware.run(rootSaga);
export default store;

6.3 store\reducer.tsx #

src\store\reducer.tsx

import { AnyAction } from 'redux';
+import * as types from './action-types';
+export interface CounterState {
+    number: number
+};
+let initialState = { number: 0 };
+export default function (state: CounterState = initialState, action: AnyAction): CounterState {
+    switch (action.type) {
+        case types.INCREMENT:
+            return { number: state.number + 1 };
+        default:
+            return state;
+    }
+}

6.4 store\sagas.tsx #

src\store\sagas.tsx

import { delay, all, put, takeEvery } from 'redux-saga/effects'
//worker Saga: 将执行异步的 increment 任务
//incrementAsync Saga 通过 delay(1000) 延迟了 1 秒钟,然后 dispatch 一个叫 INCREMENT 的 action。
export function* incrementAsync() {
    //工具函数 delay,这个函数返回一个延迟 1 秒再 resolve 的 Promise 我们将使用这个函数去 block(阻塞) Generator
    //Sagas 被实现为 Generator functions,它会 yield 对象到 redux-saga middleware。 被 yield 的对象都是一类指令,指令可被 middleware 解释执行。当 middleware 取得一个 yield 后的 Promise,middleware 会暂停 Saga,直到 Promise 完成
    //incrementAsync 这个 Saga 会暂停直到 delay 返回的 Promise 被 resolve,这个 Promise 将在 1 秒后 resolve
    //当 middleware 拿到一个被 Saga yield 的 Effect,它会暂停 Saga,直到 Effect 执行完成,然后 Saga 会再次被恢复
    yield delay(1000)
    //一旦 Promise 被 resolve,middleware 会恢复 Saga 接着执行,直到遇到下一个 yield
    //在这里下一个语句是另一个被 yield 的对象:调用 put({type: 'INCREMENT'}) 的结果,意思是告诉 middleware 发起一个 INCREMENT 的 action
    //put 就是我们称作 Effect 的一个例子。Effects 是一些简单 Javascript 对象,包含了要被 middleware 执行的指令
    yield put({ type: 'INCREMENT' })
}
//takeEvery,用于监听所有的 INCREMENT_ASYNC action,并在 action 被匹配时执行 incrementAsync 任务
export function* watchIncrementAsync() {
    yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

export function* helloSaga() {
    console.log('Hello Saga!');
}

export default function* rootSaga() {
    //这个 Saga yield 了一个数组,值是调用 helloSaga 和 watchIncrementAsync 两个 Saga 的结果。意思是说这两个 Generators 将会同时启动
    yield all([
        helloSaga(),
        watchIncrementAsync()
    ])
}

6.5 action-types.tsx #

src\store\action-types.tsx

export const INCREMENT = 'INCREMENT';
export const INCREMENT_ASYNC = 'INCREMENT_ASYNC';

6.6 store\actions.tsx #

src\store\actions.tsx

import * as types from './action-types';
export default {
    incrementAsync() {
        return { type: types.INCREMENT_ASYNC }
    }
}

6.7 components\Counter.tsx #

src\components\Counter.tsx

import React, { Component } from 'react'
import { connect } from 'react-redux';
import actions from '../store/actions';
import { CounterState } from '../store/reducer';
type Props = CounterState & typeof actions;
class Counter extends Component<Props> {
    render() {
        return (
            <div>
                <p>{this.props.number}</p>
                <button onClick={this.props.incrementAsync}>+</button>
            </div>
        )
    }
}

export default connect(
    (state: CounterState): CounterState => state,
    actions
)(Counter);

7. 声明式effects #

  • 在 redux-saga 的世界里,Sagas 都用 Generator 函数实现。我们从 Generator 里 yield 纯 JavaScript 对象以表达 Saga 逻辑
  • 我们称呼那些对象为 Effect。Effect 是一个简单的对象,这个对象包含了一些给 middleware 解释执行的信息
  • 你可以把 Effect 看作是发送给 middleware 的指令以执行某些操作(调用某些异步函数,发起一个 action 到 store,等等)
  • cps(fn, ...args) 创建一个 Effect 描述信息,用来命令 middleware 以 Node 风格的函数(Node style function)的方式调用 fn
  • call(fn, ...args) 创建一个 Effect 描述信息,用来命令 middleware 以参数 args 调用函数 fn
  • call([context, fn], ...args) 类似 call(fn, ...args),但支持传递 this 上下文给 fn,在调用对象方法时很有用
  • apply(context, fn, [args]) call([context, fn], ...args) 的另一种写法

7.1 src\utils.tsx #

src\utils.tsx

export const delay = (ms: number) => {
    return new Promise(function (resolve) {
        setTimeout(() => {
            resolve();
        }, ms);
    });
}

export function read(filename: string, callback: any) {
    setTimeout(function () {
        console.log('read', filename);
        callback(null, filename);
    }, 1000);
}

7.2 src\store\sagas.tsx #

src\store\sagas.tsx

import { all, put, takeEvery, call, takeLatest, cps, apply } from 'redux-saga/effects'
import { delay, read } from '../utils';

export function* readAsync() {
    let content = yield cps(read, '1.txt');//cps(fn, ...args)
    console.log('content=', content);
}

export function* incrementAsync() {
    //工具函数 delay,这个函数返回一个延迟 1 秒再 resolve 的 Promise 我们将使用这个函数去 block(阻塞) Generator
    //Sagas 被实现为 Generator functions,它会 yield 对象到 redux-saga middleware。 被 yield 的对象都是一类指令,指令可被 middleware 解释执行。当 middleware 取得一个 yield 后的 Promise,middleware 会暂停 Saga,直到 Promise 完成
    //incrementAsync 这个 Saga 会暂停直到 delay 返回的 Promise 被 resolve,这个 Promise 将在 1 秒后 resolve
    //put 还是 call 都不执行任何 dispatch 或异步调用,它们只是简单地返回 plain Javascript 对象
    yield call(delay, 3000);//call(fn, ...args)
    //yield call([null,delay],3000);  call([context, fn], ...args)
    //yield apply(null,delay,3000); apply(context, fn, args)
    //yield delay(3000);
    yield put({ type: 'INCREMENT' })
}
export default function* rootSaga() {
    //这个 Saga yield 了一个数组,值是调用 helloSaga 和 watchIncrementAsync 两个 Saga 的结果。意思是说这两个 Generators 将会同时启动
    yield all([
        incrementAsync()
    ])
}

8. 错误处理 #

8.1 try catch #

  • 我们可以使用熟悉的 try/catch 语法在 Saga 中捕获错误
import { all, put, takeEvery, call, takeLatest, cps, apply } from 'redux-saga/effects'
import { delay, read } from '../utils';
+export const delay2 = (ms: number) => new Promise((resolve, reject) => {
+    setTimeout(() => {
+        if (Math.random() > .5) {
+            resolve();
+        } else {
+            reject();
+        }
+    }, ms);
+});
+export function* incrementAsync2() {
+    try {
+        yield call(delay2, 3000);
+        yield put({ type: 'INCREMENT' });
+        alert('操作成功');
+    } catch (error) {
+        alert('操作失败');
+    }
+}
+//takeEvery,用于监听所有的 INCREMENT_ASYNC action,并在 action 被匹配时执行 incrementAsync 任务
+export function* watchIncrementAsync() {
+    //yield takeEvery('INCREMENT_ASYNC', incrementAsync);
+    //只想得到最新那个请求的响应,如果已经有一个任务在执行的时候启动另一个 fetchData ,那之前的这个任务会被自动取消
+    yield takeLatest('INCREMENT_ASYNC', incrementAsync2);
+}
export default function* rootSaga() {
    //这个 Saga yield 了一个数组,值是调用 helloSaga 和 watchIncrementAsync 两个 Saga 的结果。意思是说这两个 Generators 将会同时启动
    yield all([
        watchIncrementAsync()
    ])
}

8.2 错误标识 #

  • 你也可以让你的 API 服务返回一个正常的含有错误标识的值 src\store\sagas.js
import { all, put, takeEvery, call, takeLatest, cps, apply } from 'redux-saga/effects'
import { delay, read } from '../utils';
+export const delay3 = (ms:number) => new Promise((resolve, reject) => {
+    setTimeout(() => {
+        let data = Math.random();
+        resolve({
+            code: data > .5 ? 0 : 1,
+            data
+        });
+    }, ms);
+});
+export function* incrementAsync3() {
+    let { code, data } = yield call(delay3, 1000);
+    if (code === 0) {
+        yield put({ type: 'INCREMENT' });
+        alert('操作成功 data=' + data);
+    } else {
+        alert('操作失败');
+    }
+}
+//takeEvery,用于监听所有的 INCREMENT_ASYNC action,并在 action 被匹配时执行 incrementAsync 任务
export function* watchIncrementAsync() {
    //yield takeEvery('INCREMENT_ASYNC', incrementAsync);
    //只想得到最新那个请求的响应,如果已经有一个任务在执行的时候启动另一个 fetchData ,那之前的这个任务会被自动取消
+    yield takeLatest('INCREMENT_ASYNC', incrementAsync3);
}
export default function* rootSaga() {
    //这个 Saga yield 了一个数组,值是调用 helloSaga 和 watchIncrementAsync 两个 Saga 的结果。意思是说这两个 Generators 将会同时启动
    yield all([
        watchIncrementAsync()
    ])
}

9. take #

  • takeEvery 只是一个在强大的低阶 API 之上构建的 wrapper effect
  • take 就像我们更早之前看到的 call 和 put。它创建另一个命令对象,告诉 middleware 等待一个特定的 action
import { all, put, take, select } from 'redux-saga/effects'
import { INCREMENT_ASYNC, INCREMENT } from './action-types';

export function* watchIncrementAsync() {
    for (let i = 0; i < 3; i++) {
        const action = yield take(INCREMENT_ASYNC);
        console.log(action);
        yield put({ type: INCREMENT });
    }
    alert('最多只能点三次!');
}
export function* watchAndLog() {
    //take 就像我们更早之前看到的 call 和 put。它创建另一个命令对象,告诉 middleware 等待一个特定的 action
    //在 take 的情况中,它将会暂停 Generator 直到一个匹配的 action 被发起了
    //在 takeEvery 的情况中,被调用的任务无法控制何时被调用, 它们将在每次 action 被匹配时一遍又一遍地被调用。并且它们也无法控制何时停止监听
    //在 take 的情况中,控制恰恰相反。与 action 被 推向(pushed) 任务处理函数不同,Saga 是自己主动 拉取(pulling) action 的。 看起来就像是 Saga 在执行一个普通的函数调用 action = getNextAction(),这个函数将在 action 被发起时 resolve
    while (true) {
        let action = yield take('*');
        const state = yield select();
        console.log('action', action);
        console.log('state after', state);
    }
}

export default function* rootSaga() {
    yield all([
        watchAndLog(),
        watchIncrementAsync()
    ])
}

10. 登陆流程 #

10.1 src\index.tsx #

src\index.tsx

import React from 'react'
import ReactDOM from 'react-dom';
+import Login from './components/Login';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(<Provider store={store}>
+    <Login />
</Provider>, document.querySelector('#root'));

10.2 action-types.tsx #

src\store\action-types.tsx

export const INCREMENT = 'INCREMENT';
export const INCREMENT_ASYNC = 'INCREMENT_ASYNC';

+export const LOGIN_REQUEST = 'LOGIN_REQUEST';
+export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
+export const SET_USERNAME = 'SET_USERNAME';
+export const LOGIN_ERROR = 'LOGIN_ERROR';
+export const LOGOUT = 'LOGOUT';

10.3 store\actions.tsx #

src\store\actions.tsx

import * as types from './action-types';
export default {
    incrementAsync() {
        return { type: types.INCREMENT_ASYNC }
    },
+   login(username: string, password: string) {
+        return { type: types.LOGIN_REQUEST, username, password }
+    },
+    logout() {
+        return { type: types.LOGOUT }
+   }
}

10.4 store\reducer.tsx #

src\store\reducer.tsx

import { AnyAction } from 'redux';
import * as types from './action-types';
+export interface CounterState {
+    number?: number;
+    username?: string | null;
+    error?: any;
+};
let initialState = { number: 0 };
export default function (state: CounterState = initialState, action: AnyAction): CounterState {
    switch (action.type) {
        case types.INCREMENT:
            return { number: state.number! + 1 };
+        case types.LOGIN_ERROR:
+            return { error: action.error };
+        case types.SET_USERNAME:
+            return { username: action.username };
+        default:
+            return state;
+    }
}

10.5 src\store\sagas.tsx #

src\store\sagas.tsx

import { call, all, put, take } from "redux-saga/effects";
import { LOGIN_ERROR, LOGIN_REQUEST, SET_USERNAME, LOGOUT } from "./action-types";
import Api from "../Api";
//首先我们创建了一个独立的 Generator login,它将执行真实的 API 调用并在成功后通知 Store。
function* login(username: string, password: string) {
    try {
        //如果 Api 调用成功了,login 将发起一个 LOGIN_SUCCESS action 然后返回获取到的 token。 如果调用导致了错误,将会发起一个 LOGIN_ERROR action。
        const token = yield call(Api.login, username, password);
        return token;
    } catch (error) {
        alert(error);
        //在 login 失败的情况下,它将返回一个 undefined 值,这将导致 loginFlow 跳过当前处理进程并等待一个新的 LOGIN_REQUEST action
        yield put({
            type: LOGIN_ERROR,
            error
        });
    }
}

function* loginFlow() {
    //一旦到达流程最后一步(LOGOUT),通过等待一个新的 LOGIN_REQUEST action 来启动一个新的迭代
    while (true) {
        //loginFlow 首先等待一个 LOGIN_REQUEST action,然后调用一个 call 到 login 任务
        //call 不仅可以用来调用返回 Promise 的函数。我们也可以用它来调用其他 Generator 函数
        //loginFlow 将等待 login 直到它终止或返回(即执行 api 调用后,发起 action 然后返回 token 至 loginFlow)
        const { username, password } = yield take(LOGIN_REQUEST);
        const token = yield call(login, username, password);
        //在 login 失败的情况下,它将返回一个 undefined 值,这将导致 loginFlow 跳过当前处理进程并等待一个新的 LOGIN_REQUEST action
        if (token) {
            yield put({
                type: SET_USERNAME,
                username
            });
            //如果调用 login 成功,loginFlow 将在 DOM storage 中存储返回的 token,并等待 LOGOUT action
            Api.storeItem("token", token);
            //当用户登出,我们删除存储的 token 并等待一个新的用户登录
            yield take(LOGOUT);
            Api.clearItem("token");
            yield put({
                type: SET_USERNAME,
                username: null
            });
        }
    }
}

export default function* rootSaga() {
    yield all([loginFlow()]);
}

10.6 src\Api.tsx #

src\Api.tsx

export default {
    login(username: string, password: string) {
        return new Promise(function (resolve, reject) {
            setTimeout(() => {
                if (Math.random() > .5) {
                    resolve(username + '-' + password);
                } else {
                    reject('登录失败');
                }
            }, 1000);
        });
    },
    storeItem(key: string, value: string) {
        localStorage.setItem(key, value);
    },
    clearItem(key: string) {
        localStorage.removeItem(key);
    }
}

10.7 components\Login.tsx #

src\components\Login.tsx

import React, { Component, RefObject } from 'react'
import { connect } from 'react-redux';
import actions from '../store/actions';
import { CounterState } from '../store/reducer';
type Props = CounterState & typeof actions;
class Login extends Component<Props> {
    username: RefObject<HTMLInputElement>;
    password: RefObject<HTMLInputElement>;
    constructor(props: any) {
        super(props);
        this.username = React.createRef<HTMLInputElement>();
        this.password = React.createRef<HTMLInputElement>();
    }
    login = (event: any) => {
        event.preventDefault();
        let username = this.username.current!.value;
        let password = this.password.current!.value;
        this.props.login(username, password);
    }
    logout = (event: any) => {
        event.preventDefault();
        this.props.logout();
    }
    render() {
        let { username } = this.props;
        let loginForm = (
            <form>
                <label>用户名</label><input ref={this.username} /><br />
                <label>密码</label><input ref={this.password} /><br />
                <button onClick={this.login}>登录</button>
            </form>
        )
        let logoutForm = (
            <form >
                用户名:{username}<br />
                <button onClick={this.logout}>退出</button>
            </form>
        )
        return (
            username ? logoutForm : loginForm
        )
    }
}
export default connect(
    (state: CounterState): CounterState => state,
    actions
)(Login);

11. fork #

  • 当 loginFlow 在 login 中被阻塞了,最终发生在开始调用和收到响应之间的 LOGOUT 将会被错过
  • 我们需要的是一些非阻塞调用login
  • 为了表示无阻塞调用,redux-saga 提供了另一个 Effect:fork,当我们 fork 一个 任务,任务会在后台启动,调用者也可以继续它自己的流程,而不用等待被 fork 的任务结束

11.1 sagas.tsx #

src\store\sagas.tsx

import { call, all, put, take, fork } from "redux-saga/effects";
import { LOGIN_ERROR, LOGIN_REQUEST, SET_USERNAME, LOGOUT, LOGIN_SUCCESS } from "./action-types";
import Api from "../Api";
+function* login(username: string, password: string) {
+    try {
+        //如果 Api 调用成功了,login 将发起一个 LOGIN_SUCCESS action 然后返回获取到的 token。 如果调用导致了错误,将会发起一个 LOGIN_ERROR action。
+        const token = yield call(Api.login, username, password);
+        yield put({ type: LOGIN_SUCCESS, token });
+        yield put({ type: SET_USERNAME, username });
+        //如果调用 login 成功,loginFlow 将在 DOM storage 中存储返回的 token,并等待 LOGOUT action   
+        Api.storeItem('token', token);
+    } catch (error) {
+        //在 login 失败的情况下,它将返回一个 undefined 值,这将导致 loginFlow 跳过当前处理进程并等待一个新的 LOGIN_REQUEST action
+        yield put({ type: LOGIN_ERROR, error });
+    }
+}

+function* loginFlow() {
+    //一旦到达流程最后一步(LOGOUT),通过等待一个新的 LOGIN_REQUEST action 来启动一个新的迭代
+    while (true) {
+        //loginFlow 首先等待一个 LOGIN_REQUEST action,然后调用一个 call 到 login 任务
+        //call 不仅可以用来调用返回 Promise 的函数。我们也可以用它来调用其他 Generator 函数
+        //loginFlow 将等待 login 直到它终止或返回(即执行 api 调用后,发起 action 然后返回 token 至 loginFlow)
+        const { username, password } = yield take(LOGIN_REQUEST);
+        //自从 login 的 action 在后台启动之后,我们获取不到 token 的结果,所以我们需要将 token 存储操作移到 login 任务内部
+        yield fork(login, username, password);
+        //yield take(['LOGOUT', 'LOGIN_ERROR'])。意思是监听 2 个并发的 action
+        //如果 login 任务在用户登出之前成功了,它将会发起一个 LOGIN_SUCCESS action 然后结束。 然后 loginFlow Saga 只会等待一个未来的 LOGOUT action 被发起
+        //如果 login 在用户登出之前失败了,它将会发起一个 LOGIN_ERROR action 然后结束
+        //如果在 login 结束之前,用户就登出了,那么 loginFlow 将收到一个 LOGOUT action 并且也会等待下一个 LOGIN_REQUEST
+        yield take([LOGOUT, LOGIN_ERROR]);
+        Api.clearItem('token');
+    }
+}

export default function* rootSaga() {
    yield all([loginFlow()]);
}

12. 取消任务 #

  • 如果我们在 API 调用期间收到一个 LOGOUT action,我们必须要 取消 login 处理进程,否则将有 2 个并发的任务, 并且 login 任务将会继续运行,并在成功的响应(或失败的响应)返回后发起一个 LOGIN_SUCCESS action(或一个 LOGIN_ERROR action),而这将导致状态不一致
  • cancel Effect 不会粗暴地结束我们的 login 任务,相反它会给予一个机会执行清理的逻辑,在 finally 区块可以处理任何的取消逻辑(以及其他类型的完成逻辑)

12.1 components\Login.tsx #

src\components\Login.tsx

import React, { Component, RefObject } from 'react'
import { connect } from 'react-redux';
import actions from '../store/actions';
import { CounterState } from '../store/reducer';
type Props = CounterState & typeof actions;
class Login extends Component<Props> {
    username: RefObject<HTMLInputElement>;
    password: RefObject<HTMLInputElement>;
    constructor(props: any) {
        super(props);
        this.username = React.createRef<HTMLInputElement>();
        this.password = React.createRef<HTMLInputElement>();
    }
    login = (event: any) => {
        event.preventDefault();
        let username = this.username.current!.value;
        let password = this.password.current!.value;
        this.props.login(username, password);
    }
    logout = (event: any) => {
        event.preventDefault();
        this.props.logout();
    }
    render() {
        let { username } = this.props;
        let loginForm = (
            <form>
                <label>用户名</label><input ref={this.username} /><br />
                <label>密码</label><input ref={this.password} /><br />
                <button onClick={this.login}>登录</button>
+                <button onClick={this.logout}>退出</button>
            </form>
        )
        let logoutForm = (
            <form >
                用户名:{username}<br />
                <button onClick={this.logout}>退出</button>
            </form>
        )
        return (
            username ? logoutForm : loginForm
        )
    }
}
export default connect(
    (state: CounterState): CounterState => state,
    actions
)(Login);

12.2 sagas.tsx #

src\store\sagas.tsx

import { call, all, put, take, fork, cancelled, cancel } from "redux-saga/effects";
+import { LOGIN_ERROR, LOGIN_REQUEST, SET_USERNAME, LOGOUT, LOGIN_SUCCESS } from "./action-types";
import Api from "../Api";
function* login(username: string, password: string) {
    try {
        //如果 Api 调用成功了,login 将发起一个 LOGIN_SUCCESS action 然后返回获取到的 token。 如果调用导致了错误,将会发起一个 LOGIN_ERROR action。
+        Api.storeItem('loading', 'true');
        const token = yield call(Api.login, username, password);
        yield put({ type: LOGIN_SUCCESS, token });
        yield put({ type: SET_USERNAME, username });
        //如果调用 login 成功,loginFlow 将在 DOM storage 中存储返回的 token,并等待 LOGOUT action   
        Api.storeItem('token', token);
+        Api.storeItem('loading', 'false');
    } catch (error) {
        //在 login 失败的情况下,它将返回一个 undefined 值,这将导致 loginFlow 跳过当前处理进程并等待一个新的 LOGIN_REQUEST action
        yield put({ type: LOGIN_ERROR, error });
        Api.storeItem('loading', 'false');
+    } finally {
+        if (yield cancelled()) {
+            // ... put special cancellation handling code here
+            Api.storeItem('loading', 'false');
+        }
+    }
}

function* loginFlow() {
    //一旦到达流程最后一步(LOGOUT),通过等待一个新的 LOGIN_REQUEST action 来启动一个新的迭代
    while (true) {
        //loginFlow 首先等待一个 LOGIN_REQUEST action,然后调用一个 call 到 login 任务
        //call 不仅可以用来调用返回 Promise 的函数。我们也可以用它来调用其他 Generator 函数
        //loginFlow 将等待 login 直到它终止或返回(即执行 api 调用后,发起 action 然后返回 token 至 loginFlow)
        const { username, password } = yield take(LOGIN_REQUEST);
        //自从 login 的 action 在后台启动之后,我们获取不到 token 的结果,所以我们需要将 token 存储操作移到 login 任务内部
+        const task = yield fork(login, username, password);
        //yield take(['LOGOUT', 'LOGIN_ERROR'])。意思是监听 2 个并发的 action
        //如果 login 任务在用户登出之前成功了,它将会发起一个 LOGIN_SUCCESS action 然后结束。 然后 loginFlow Saga 只会等待一个未来的 LOGOUT action 被发起
        //如果 login 在用户登出之前失败了,它将会发起一个 LOGIN_ERROR action 然后结束
        //如果在 login 结束之前,用户就登出了,那么 loginFlow 将收到一个 LOGOUT action 并且也会等待下一个 LOGIN_REQUEST
+        const action = yield take([LOGOUT, LOGIN_ERROR]);
        //将task 传入给 cancel Effect。 如果任务仍在运行,它会被中止,如果任务已完成,那什么也不会发生
+        if (action.type == LOGOUT) {
+            yield cancel(task);
+        }
        Api.clearItem('token');
    }
}

export default function* rootSaga() {
    yield all([loginFlow()]);
}

13. race #

13.1 src\index.tsx #

src\index.tsx

import React from 'react'
import ReactDOM from 'react-dom';
import Recorder from './components/Recorder';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(<Provider store={store}>
    <Recorder />
</Provider>, document.querySelector('#root'));

13.2 src\store\actions.tsx #

src\store\actions.tsx

import * as types from './action-types';
export default {
    incrementAsync() {
        return { type: types.INCREMENT_ASYNC }
    },
    login(username: string, password: string) {
        return { type: types.LOGIN_REQUEST, username, password }
    },
    logout() {
        return { type: types.LOGOUT }
    },
    stop() {
        return { type: types.CANCEL_TASK }
    }
}

13.3 action-types.tsx #

src\store\action-types.tsx

export const INCREMENT = 'INCREMENT';
export const INCREMENT_ASYNC = 'INCREMENT_ASYNC';

export const LOGIN_REQUEST = 'LOGIN_REQUEST';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const SET_USERNAME = 'SET_USERNAME';
export const LOGIN_ERROR = 'LOGIN_ERROR';
export const LOGOUT = 'LOGOUT';
+export const CANCEL_TASK = 'CANCEL_TASK';

13.4 src\store\sagas.tsx #

src\store\sagas.tsx

import { call, all, put, take, race } from 'redux-saga/effects'
import { INCREMENT, CANCEL_TASK } from './action-types';
import { delay } from '../utils';
/**
 * 有时候我们同时启动多个任务,但又不想等待所有任务完成,我们只希望拿到 胜利者:即第一个被 resolve(或 reject)的任务
 * a=1000 b=undefined
 */
function* raceFlow() {
    const { a, b } = yield race({
        a: call(delay, 1000),
        b: call(delay, 2000)
    });
    console.log('a=' + a, 'b=' + b);
}

//每隔一秒钟让数字加1

function* start() {
    while (true) {
        yield call(delay, 1000);
        yield put({ type: INCREMENT });
    }
}
//race 的另一个有用的功能是,它会自动取消那些失败的 Effects
function* recorder() {
    yield race({
        start: call(start),
        stop: take(CANCEL_TASK)
    });
}
export default function* rootSaga() {
    //yield all([raceFlow()])
    yield all([recorder()])
}

13.5 Recorder.tsx #

src\components\Recorder.tsx

import React, { Component } from 'react'
import { connect } from 'react-redux';
import actions from '../store/actions';
import { CounterState } from '../store/reducer';
type Props = CounterState & typeof actions;
class Counter extends Component<Props> {
    render() {
        return (
            <div>
                <p>{this.props.number}</p>
                <button onClick={this.props.stop}>停止</button>
            </div>
        )
    }
}
export default connect(
    state => state,
    actions
)(Counter);

访问验证

请输入访问令牌

Token不正确,请重新输入