redux-saga 就是用来处理上述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为你的应用管理复杂的流程。
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
import store from './store';
console.log(store);
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;
src\store\reducer.tsx
import { AnyAction } from 'redux';
export interface CounterState { };
let initialState = {};
export default function (state: CounterState = initialState, action: AnyAction): CounterState {
return state;
}
src\store\sagas.tsx
export function* helloSaga() {
console.log('Hello Saga!');
}
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'));
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;
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;
+ }
+}
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()
])
}
src\store\action-types.tsx
export const INCREMENT = 'INCREMENT';
export const INCREMENT_ASYNC = 'INCREMENT_ASYNC';
src\store\actions.tsx
import * as types from './action-types';
export default {
incrementAsync() {
return { type: types.INCREMENT_ASYNC }
}
}
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);
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);
}
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()
])
}
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()
])
}
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()
])
}
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()
])
}
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'));
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';
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 }
+ }
}
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;
+ }
}
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()]);
}
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);
}
}
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);
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()]);
}
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);
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()]);
}
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'));
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 }
}
}
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';
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()])
}
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);