Redux 使用及原理
# 工作流

Redux 中 只有一个
state
树,存在以唯一的一个store
。
# 核心概念
# store
store 是状态数用来保存数据,它的常用的三个属性及方法 dispatch
,getState
,subscribe
。
dispatch(action)
方法接收一个 action
用来作为处理数据的类型,它返回传入的 action
,初始化时会自动执行一次。
getState()
方法返回状态数据。
subscribe(func)=>fun
方法接收一个函数,当数据更新后,传入的函数就会被自动执行。此函数返回一个 unsubscribe
函数,他用来解绑传入的函数,此后数据更新,一开始传入的函数将不再执行。
# reducer
reducer(state, action)
函数接收两个参数,参数一是状态,参数二是 action
代表处理数据的类型及参数。此函数返回一个新的 state
。可以设置 state
的默认值作为 state
的初始值。
# action
是一个纯对象,必须包含 type
属性,type
属性用来标识处理数据的类型,还可以设置其他值用来传参。
let INCREMENT = "increment"
let = action = {
type: INCREMENT
}
2
3
4
# createStore
createStore(reducer, initalState)
函数接收两个参数,第一个参数是 reducer
,第二个是初始数据状态 state
的值。如果 reducer
中 state
没有设置默认值,那么就可以通过此处设置初始值。此函数返回 store
对象。
# 使用
import { createStore } from "./redux/index";
//初始状态值
let initState = 0;
// 用来标识处理数据的方式类型
const DECREMENT = Symbol.for("decrement");
const INCREMENT = Symbol.for("increment");
// action 必须有 type 属性
// reducer 处理数据,返回新的 state
function reducer(state, action) {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
}
// 创建 store
let store = createStore(reducer, initState);
// 订阅函数,数据更新后自动执行。返回解绑函数。
let unsubscribe = store.subscribe(() => {
console.log("刷新", store.getState());
});
//获取 state 的值
let state = store.getState(); // store.getState() 获取state
console.log(state);
// 触发数据更新 执行 reducer
function add() {
store.dispatch({ type: INCREMENT });
}
setInterval(() => {
add();
}, 1000);
// 解绑订阅的函数
setTimeout(() => {
unsubscribe();
}, 5000);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
组件中使用
import React, { Component } from "react";
import { createStore } from "redux";
let INCREMENT = "increment";
let DECREMENT = "decrement";
let initialState = 0;
let reducer = (state, action) => {
switch (action.type) {
case INCREMENT:
return state + 1;
case DECREMENT:
return state - 1;
default:
return state;
}
};
let store = createStore(reducer, initialState);
export default class Counter extends Component {
state = { number: store.getState() };
componentDidMount() {
// 订阅this.setState
store.subscribe(() => {
this.setState({
number: store.getState(),
});
});
}
componentWillUnmount() {
this.unsubscribe();
}
increment = () => {
store.dispatch({ type: INCREMENT });
};
decrement = () => {
store.dispatch({ type: DECREMENT });
};
render() {
return (
<div>
<div>{this.state.number}</div>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
</div>
);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 手写实现
crreateState.js
export default function createStore(reducer, initState) {
if (typeof reducer !== "function")
throw new Error("reducer must be a function type!");
let state = initState;
let subscribeFuncs = [];
//派发
function dispatch(action) {
if (!isActionType(action)) throw new Error("action`s type is error!");
if (action.type === undefined) throw new Error("action`s type is error!");
state = reducer(state, action);
for (let i = 0; i < subscribeFuncs.length; i++) {
const fun = subscribeFuncs[i];
fun();
}
return action;
}
//订阅
function subscribe(func) {
let isSubscribe = true;
subscribeFuncs.push(func);
return function () {
if (isSubscribe) {
// 返回取消订阅函数
let index = subscribeFuncs.indexOf(func);
subscribeFuncs.splice(index, 1);
isSubscribe = false;
}
};
}
function getState() {
return state;
}
dispatch({ type: "9090909" });
return {
dispatch,
subscribe,
getState,
};
}
// 判断是不是 actino 是不是 纯对象
function isActionType(action) {
if (typeof action !== "object" || typeof action === null) return false;
let proto = action;
while (Object.getPrototypeOf(proto)) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(action) === proto;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 判断纯对象
function isActionType(action) {
if (typeof action !== "object" || typeof action === null) return false;
let proto = action;
while (Object.getPrototypeOf(proto)) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(action) === proto;
}
2
3
4
5
6
7
8
# bindActionCreators
为了方便高校的得到 action
我们把 action
,用函数去创建它,并把这些函数统一的存入一个对象。如下:
let actionCreators = {
increment(value) {
//使用value
return { type: INCEMENT, someProp: value };
},
decrement(value) {
//使用value
// ...
return { type: DECEMENT };
},
};
2
3
4
5
6
7
8
9
10
11
通过执行函数就可以拿到对应的 action
。
bindActionCreators
的作用就是将这些创建 action
的函数改造为分发(dispatch
)action
的函数。把上面的 actionCreators
变成如下的 dispatchActions
。
使用方式
let dispatchActions = bindActionsCreators(actionCreators, store.dispatch);
let dispatchActions = {
increment(value) {
store.dispatch({ type: INCEMENT, someProp: value };);
},
decrement(value) {
store.dispatch({ type: DECEMENT });
},
};
2
3
4
5
6
7
8
这样我们就可以直接调用 bindActionCreators
中的函数直接分发 action
了。
实现如下:
let bindActionsCreator = (actionsCreator, dispatch) => {
return function () {
return dispatch(actionsCreator.apply(this, arguments));
};
};
let bindActionsCreators = (actions, dispatch) => {
if (typeof actions === "function") {
return bindActionsCreator(actions, dispatch);
} else {
let bindActions = {};
for (const key in actions) {
const actionsCreator = actions[key];
bindActions[key] = bindActionsCreator(actionsCreator, dispatch);
}
return bindActions;
}
};
export default bindActionsCreators;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# CombineReducers
大型的应用,状态操作太多,不可能把所有的状态操作行为都房子一个 reducer
中,放在一起太乱,不好维护。如下目录,把多个 reducer
写成不同的文件,最后在 index.js
中使用 CombineReducers
方法汇总。
CombineReducers
方法返回一个新的总的 reducers
函数,reducers
函数中包含所有的子reducer
函数,返回的状态值也是一个对象,键为每一个 reducer
传入的键名。

// index.js
import { combineReducers } from "redux";
import counter1 from "./counter1";
import counter2 from "./counter2";
let reducers = combineReducers1({
counter1,
counter2,
});
export default reducers;
2
3
4
5
6
7
8
9
10
11
使用的时候通过 store.getState()
获取到的 state
就是一个对象,每一个键对应一个 reducer
的返回值。
let state = store.getState()
console.log(state) // ==> {counter1: 11, counter2: 12}
2
通过 createStore
设置默认值时也是通过对象的形式。
let initialState = { counter1: 10, counter2: 11 }; // state 的初始值
let store = createStore(reducers, initialState);
2
实现
function combineReducers(reducers) {
return (state, action) => { // 返回一个新的总的 reducers 函数,其中包含所有 reducer 的执行结果。
let newstate = {}; // 定义新状态对象。
for (const key in reducers) {
const reducer = reducers[key];
newstate[key] = reducer(state[key], action);
}
return newstate; // 返回新状态对象
};
}
2
3
4
5
6
7
8
9
10
# 中间件
# Redux中间件机制
Redux本身就提供了非常强大的数据流管理功能,但这并不是它唯一的强大之处,它还提供了利用中间件来扩展自身功能,以满足用户的开发需求。首先我们来看中间件的定义:
It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer.
这是Dan Abramov 对middleware的描述。简单来讲,Redux middleware 提供了一个分类处理 action 的机会。在 middleware 中,我们可以检阅每一个流过的 action,并挑选出特定类型的 action 进行相应操作,以此来改变 action。这样说起来可能会有点抽象,我们直接来看图,这是在没有中间件情况下的 redux 的数据流:

输入图片说明
上面是很典型的一次 redux 的数据流的过程,但在增加了 middleware 后,我们就可以在这途中对 action 进行截获,并进行改变。且由于业务场景的多样性,单纯的修改 dispatch 和 reduce 人显然不能满足大家的需要,因此对 redux middleware 的设计是可以自由组合,自由插拔的插件机制。也正是由于这个机制,我们在使用 middleware 时,我们可以通过串联不同的 middleware 来满足日常的开发,每一个 middleware 都可以处理一个相对独立的业务需求且相互串联:

如上图所示,派发给 redux Store 的 action 对象,会被 Store 上的多个中间件依次处理,如果把 action 和当前的 state 交给 reducer 处理的过程看做默认存在的中间件,那么其实所有的对 action 的处理都可以有中间件组成的。值得注意的是这些中间件会按照指定的顺序一次处理传入的 action,只有排在前面的中间件完成任务之后,后面的中间件才有机会继续处理 action,同样的,每个中间件都有自己的“熔断”处理,当它认为这个 action 不需要后面的中间件进行处理时,后面的中间件也就不能再对这个 action 进行处理了。
而不同的中间件之所以可以组合使用,是因为 Redux 要求所有的中间件必须提供统一的接口,每个中间件的尉氏县逻辑虽然不一样,但只要遵循统一的接口就能和redux以及其他的中间件对话了。
# 理解中间价的机制
中间件实质上就是重新修改了 store
的 dispatch
方法,把中间件一层套一层形成洋葱模型,从外到内再从内到外。使用 applyMiddleware 来加载中间件。
let store = applyMiddleware(miidleware)(createStore)(reducers,initialState);
因此我们首先可以看一下 redux 中关于 applyMiddleware
的源码:
export default (...middleWares) => {
return (createStore) => {
return (reducer, initialState) => {
let store = createStore(reducer, initialState);
let dispatch = () => {
throw new Error("现在不能调用!");
};
let middleWareAPI = {
dispatch: (...args) => store.dispatch(...args),
getState: store.getState,
};
let links = middleWares.map((middleWare) => middleWare(middleWareAPI));
dispatch = compose(links)(store.dispatch);
store.dispatch = dispatch;
return store;
};
};
};
function compose(links) {
return links.reduce((acumulator, current) => (...args) =>
acumulator(current(...args))
);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
从上面的代码我们不难看出,applyMiddleware
这个函数的核心就在于在于组合 compose
,通过将不同的 middlewares
一层一层包裹到原生的 dispatch
之上,然后对 middleware
的设计采用柯里化的方式,以便于 compose
,从而可以动态产生 next
方法以及保持 store
的一致性。
说起来可能有点绕,直接来看一个啥都不干的中间件是如何实现的:
const doNothingMidddleware = (dispatch, getState) => next => action => next(action)
上面这个函数接受一个对象作为参数,对象的参数上有两个字段 dispatch
和 getState
,分别代表着 Redux Store
上的两个同名函数,但需要注意的是并不是所有的中间件都会用到这两个函数。然后 doNothingMidddleware
返回的函数接受一个 next
类型的参数,这个 next
是一个函数,如果调用了它,就代表着这个中间件完成了自己的职能,并将对 action
控制权交予下一个中间件。但需要注意的是,这个函数还不是处理 action
对象的函数,它所返回的那个以 action
为参数的函数才是。最后以 action
为参数的函数对传入的 action
对象进行处理,在这个地方可以进行操作,比如:
- 调动
dispatch
派发一个新action
对象 - 调用
getState
获得当前Redux Store
上的状态 - 调用
next
告诉Redux
当前中间件工作完毕,让Redux
调用下一个中间件 - 访问
action
对象action
上的所有数据
在具有上面这些功能后,一个中间件就足够获取 Store
上的所有信息,也具有足够能力可用之数据的流转。看完上面这个最简单的中间件,下面我们来看一下 redux
中间件内,最出名的中间件 redux-thunk
的实现:
function createThunk(withExtraArgument) {
return ({ getState, dispatch }) => (next) => (action) => {
if (typeof action === "function") {
action(getState, dispatch, withExtraArgument);
return;
}
next(action);
};
}
let thunk = createThunk();
thunk.withExtraArgument = createThunk;
export default thunk;
2
3
4
5
6
7
8
9
10
11
12
13
redux-thunk
的代码很简单,它通过函数是变成的思想来设计的,它让每个函数的功能都尽可能的小,然后通过函数的嵌套组合来实现复杂的功能,我上面写的那个最简单的中间件也是如此(当然那是个瓜皮中间件)。redux-thunk
中间件的功能也很简单。首先检查参数 action
的类型,如果是函数的话,就执行这个 action
韩湖水,并把 dispatch
, getState
, extraArgument
作为参数传递进去,否则就调用 next
让下一个中间件继续处理 action
。
需要注意的是,每个中间件最里层处理 action
参数的函数返回值都会影响 Store
上的 dispatch
函数的返回值,但每个中间件中这个函数返回值可能都不一样。就比如上面这个 react-thunk
中间件,返回的可能是一个 action
函数,也有可能返回的是下一个中间件返回的结果。因此,dispatch
函数调用的返回结果通常是不可控的,我们最好不要依赖于 dispatch
函数的返回值。
# redux的异步流
在多种中间件中,处理 redux
异步事件的中间件,绝对占有举足轻重的地位。从简单的 react-thunk
到 redux-promise
再到 redux-saga
等等,都代表这各自解决 redux
异步流管理问题的方案
# redux-thunk
前面我们已经对 redux-thunk
进行了讨论,它通过多参数的 currying
以实现对函数的惰性求值,从而将同步的 action
转为异步的 action
。在理解了redux-thunk
后,我们在实现数据请求时,action
就可以这么写
let actionCreator = {
incrementAsync(...args) {
return function (getState, dispatch) {
setTimeout(() => {
dispatch({ type: "INCREMENT" });
}, 2000);
};
},
};
2
3
4
5
6
7
8
9
尽管 redux-thunk
很简单,而且也很实用,但人总是有追求的,都追求着使用更加优雅的方法来实现 redux
异步流的控制,这就有了redux-promise
。
# redux-promise
不同的中间件都有着自己的适用场景,react-thunk
比较适合于简单的 API
请求的场景,而 Promise
则更适合于输入输出操作,比较 fetch
函数返回的结果就是一个 Promise
对象,下面就让我们来看下最简单的 Promise
对象是怎么实现的:
import { isFSA } from 'flux-standard-action';
function isPromise(val) {
return val && typeof val.then === 'function';
}
export default function promiseMiddleware({ dispatch }) {
return next => action => {
if (!isFSA(action)) {
return isPromise(action)
? action.then(dispatch)
: next(action);
}
return isPromise(action.payload)
? action.payload.then(
result => dispatch({ ...action, payload: result }),
error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
}
)
: next(action);
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
它的逻辑也很简单主要是下面两部分:
- 先判断是不是标准的
flux action
。如果不是,那么判断是否是promise
, 是的话就执行action.then(dispatch)
,否则执行next(action)
。 - 如果是, 就先判断
payload
是否是promise
,如果是的话payload.then
获取数据,然后把数据作为payload
重新dispatch({ ...action, payload: result})
;不是的话就执行next(action)
结合 redux-promise
我们就可以利用 es7
的 async
和 await
语法,来简化异步操作了,比如这样:
const fetchData = (url, params) => fetch(url, params)
async function getWeather(url, params) {
const result = await fetchData(url, params)
if (result.error) {
return {
type: 'GET_WEATHER_ERROR', error: result.error,
}
}
return {
type: 'GET_WEATHER_SUCCESS', payload: result,
}
}
# redux-saga
redux-saga
是一个管理 redux
应用异步操作的中间件,用于代替 redux-thunk
的。它通过创建 Sagas
将所有异步操作逻辑存放在一个地方进行集中处理,以此将 react
中的同步操作与异步操作区分开来,以便于后期的管理与维护。对于 Saga
,我们可简单定义如下:
Saga = Worker + Watcher
redux-saga
相当于在 Redux
原有数据流中多了一层,通过对 Action
进行监听,从而捕获到监听的 Action
,然后可以派生一个新的任务对 state
进行维护(这个看项目本身的需求),通过更改的 state
驱动 View
的变更。如下图所示:

saga
特点:
saga
的应用场景是复杂异步。- 可以使用
takeEvery
打印logger
(logger
大法好),便于测试。 - 提供
takeLatest/takeEvery/throttle
方法,可以便利的实现对事件的仅关注最近实践还是关注每一次实践的时间限频。 - 提供
cancel/delay
方法,可以便利的取消或延迟异步请求。 - 提供
race(effects),[...effects]
方法来支持竞态和并行场景。 - 提供
channel
机制支持外部事件。
Redux Saga
适用于对事件操作有细粒度需求的场景,同时它也提供了更好的可测试性,与可维护性,比较适合对异步处理要求高的大型项目,而小而简单的项目完全可以使用 redux-thunk
就足以满足自身需求了。毕竟react-thunk
对于一个项目本身而言,毫无侵入,使用极其简单,只需引入这个中间件就行了。
# 原理
- 采用
Generator
函数来yield Effects
Generator
函数作用是可以暂停执行,再次执行时从上次暂停的地方开始执行Effect
是一个简单的对象,包含一些给middleware
解释执行的信息- 可以通过
effect api
创建api
如fork call take put cancel
等来创建Effect
saga
按作用可以分为三类:worker saga
、Watcher saga
、Root saga
。
# 声明式 effects
redux-saga
的世界里 saga
都是 用 generato
r 函数实现的,我们从 generator
里 yield
纯JavaScrip
t 对象以表达 saga
逻辑。那些对象称为 effect
。都是简单对象,包含了一些给middleware 的执行所需的信息。
把 effect
看作是 发送给 middleware
的指令以执行某些操作(调用异步函数、发送 action
到 store
。。。)
几个 effect
cps(fn,...args) node
风格执行函数(回调函数)
call(fn,...args)
call([context,fun],...args)
apply(context,fn,[...args])
# 使用
下例点击延迟一秒加一。
Store.js
import { createStore, applyMiddleware } from "redux";
import reducers from "./reducers";
import createSagaMiddleWare from "redux-saga"; // 创建 saga reducer
import rootSaga from "./saga";
let sagaMiddleWare = createSagaMiddleWare();
let store = createStore(reducers, applyMiddleware(sagaMiddleWare));
sagaMiddleWare.run(rootSaga); // sagaMiddleWare 执行器 启动 generator 函数
export default store;
2
3
4
5
6
7
8
9
使用 createSagaMiddleWare
创建 sagaMiddleWare
。
创建 store , 执行 sagaMiddleWare.run(rootSaga)
是执行器用来启动 generator
函数。
saga.js
import { all, put, takeEvery} from "redux-saga/effects"; // 副作用操作
import * as types from "./action-types";
export function* incrementAsync() {
yield delay(1000);
yield put({ type: types.INCREMENT });
}
export const watchIncrementAsync = function* () {
yield takeEvery(types.ASYNC_INCREMENT, incrementAsync);
};
export default function* rootSaga() {
yield all([ watchIncrementAsync()]);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
rootSaga
函数为根 Generator
用来组织所有 Generator
函数的,all
表示其参数数组中的所有 Generator
函数都会执行。watchIncrementAsync
是一个监听 saga, 监听向仓库派发的动作,如果监听到某些动作会通知 worker saga
去执行某些操作。takeEvery
接收两个参数,参数一为一个 action type
,参数二为一个 worker saga
用来处理当相应的逻辑。每当有参数一的type 动作被派发时,第二个参数 worker saga
就会自动执行一次。 yield put(action)
直接向仓库派发 action
。 每当 yield
一个值(一般被称为 effect
),就告诉 saga
中间件执行某些处理。
# 错误处理
后台接口错误处理
后台接口失败了,会返回失败的 HTTP 状态码,客户端则通过不同的 HTTP 状态码来分支处理;后台不管及接口失败还是成功,都返回成功的 HTTP 状态码,服务端返回的不同 CODE 来标识结果成功与否,客户端通过 CODE 来分支处理。
如果有错误抛出,就使用 try catch
进行错误处理。
try {
yield call(delay, 1000);
yield put({ type: types.INCREMENT });
} catch (error) {
// hanlde error
// ...
}
2
3
4
5
6
7
如果没有报错,则通过返回的状态码判断是否错误。
let { code, data, error } = yield call(delay, 1000);
if (code === 0) {
yield put({ type: types.INCREMENT });
} else {
// hanlde error
// ...
}
2
3
4
5
6
7
# take(type)
与 takeEvery
的区别是之间听一次就不再监听。如下的循环三次监听 ASYNC_INCREMENT
动作,循环到 take(types.ASYNC_INCREMENT)
时就阻塞住,直到派发了所监听的动作向下才执行,如此循环三次监听。它返回该动作类型的 action
。
function* WatchIncrementAsync() {
for (let i = 0; i < 3; i++) {
// 监听动作, 阻塞等到动作派发才执行
yield take(types.ASYNC_INCREMENT);
yield put({ type: types.INCREMENT });
}
alert("最多执行三次");
}
export default function* rootSaga() {
yield all([
WatchIncrementAsync(), // 监听异步加一动作
]);
console.log(".....over");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
yield take("*")
监听所有的动作。
export function* WatchLog() {
while (true) {
let action = yield take("*");
// 获取最新的状态树
let state = yield select((state) => state.counter);
}
}
2
3
4
5
6
7
使用 select
获取状态树,可以接收一个参数过滤状态树。
# takeEvery(type, function*)
takeEvery
接收两个参数,参数一为一个 action type
,参数二为一个 worker saga
用来处理当相应的逻辑。每当有参数一的type 动作被派发时,第二个参数 worker saga
就会自动执行一次。takeEvery
的原理如下,使用 whil(true)
永久用 take
循环监听,这样就可以监听所指定动作的每一次派发。
function* takeEvery(type,worker) {
while (true) {
yield take(types.ASYNC_INCREMENT);
yield worker()
}
}
2
3
4
5
6
# fork
相当于开启子进程单独执行,不会阻塞当前主进程(理解)。返回得到一个任务对象。
let task = yield fork(login, payload.username, payload.password);
// .... 可以继续执行下去不会被阻塞
2
# cancel
当一个任务开启后,需要中断它就使用 cancel
。fork
返回一个任务对象,使用 cancel
来取消任务。
let task = yield fork(login, payload.username, payload.password);
let action = yield take(types.LOGOUT);
2
# cancelled
用来判断处于取消状态时,用于任务取消时要执行的后续扫尾工作。
function* login(username, password) {
try {
//..... Api调用
} catch (error) {
//....
} finally {
// 无论如何都会执行,判断是否是处于取消任务状态,如果是就执行代码
if (yield cancelled()) {
Api.setItem("loading", "false");
}
}
}
// ...
let task = yield fork(login, payload.username, payload.password);
// 取消任务,取消后执行 finally 中的代码。
let action = yield take(types.LOGOUT);
// ....
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# race
有时候我们同事启动多个任务,但又不想等所有任务完成,我们希望拿到胜利者:第一个 resolve
或 reject
的任务。race
的另一个有用的功能是,他会自动取消那些失败的 Effect
。
const delay = ms =>new Promise(function(resolve,reject){
setTimeout(()=>{
resolve(ms);
},ms);
});
export default function*(){
const {a,b} = yield race({
a:call(delay,1000),
b:call(delay,500)
});
console.log('a=',a,'b=',b) // a=undefined b=500
}
2
3
4
5
6
7
8
9
10
11
12
一开始 start
开始执行,就不断地隔一秒 INCREMENT
始终没完成。当 types.CANCEL_COUNTER
任务派发后 stop
任务就完成了,满足了 race
只要有任务完成就结束的条件,因此 start
任务就被终止,整个任务完成。
function* start() {
while (1) {
yield call(delay, 1000);
yield put({ type: types.INCREMENT });
}
}
export default function* () {
yield race({
start: call(start),
stop: take(types.CANCEL_COUNTER), // 等待一次 CANCEL_COUNTER 动作的派发
});
}
2
3
4
5
6
7
8
9
10
11
12
13
# redux-saga 部分源码实现
export default function createSagaReducer() {
function channelCreator() {
let subscribeFns = {};
window.subscribeFns = subscribeFns;
function subscribe(actionType, next) {
subscribeFns[actionType] = next;
}
function publish(action) {
if (subscribeFns[action.type]) {
let next = subscribeFns[action.type];
delete subscribeFns[action.type];
next(action);
}
}
return {
subscribe,
publish,
};
}
let channel = channelCreator();
const sagaMiddleWare = ({ getState, dispatch }) => {
sagaMiddleWare.run = (generator, preNext, Allfinish) => {
let it =
typeof generator[Symbol.iterator] == "function"
? generator
: generator();
next();
function next(arg) {
let { value: effct, done } = it.next(arg);
if (!done) {
if (typeof effct[Symbol.iterator] == "function") {
sagaMiddleWare.run(effct, next);
} else {
switch (effct.type) {
case "TAKE":
channel.subscribe(effct.actionType, next);
break;
case "PUT":
dispatch(effct.action);
next();
break;
case "CALL":
if (effct.fn(...effct.args) instanceof Promise) {
effct.fn(...effct.args).then(next);
} else {
next(effct.fn(...effct.args));
}
break;
case "CPS":
effct.fn(...effct.args, (res) => {
next(res);
});
break;
case "FORK":
let task = effct.task();
sagaMiddleWare.run(task);
next(task);
break;
case "ALL":
function times(count) {
let i = 0;
return function () {
if (++i === count) {
next();
}
};
}
let Allfinish = times(effct.workers.length);
effct.workers.forEach((worker) => {
sagaMiddleWare.run(worker, null, Allfinish);
});
break;
case "CANCEL":
effct.task.return();
next();
break;
default:
break;
}
}
} else {
preNext && preNext(effct);
Allfinish && Allfinish();
}
}
};
return (next) => (action) => {
channel.publish(action);
next(action);
};
};
return sagaMiddleWare;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
export const take = (actionType) => {
return {
type: "TAKE",
actionType,
};
};
export const put = (action) => {
return {
type: "PUT",
action,
};
};
const innerDelay = (ms) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(ms);
}, ms);
});
};
export function delay(...args) {
return call(innerDelay, ...args);
}
export function fork(task) {
return {
type: "FORK",
task,
};
}
export function* takeEvery(actionType, worker) {
yield fork(function* () {
while (true) {
yield take(actionType);
yield worker();
}
});
}
export function call(fn, ...args) {
return {
type: "CALL",
fn,
args,
};
}
export function cps(fn, ...args) {
return {
type: "CPS",
fn,
args,
};
}
export function all(workers) {
return {
type: "ALL",
workers,
};
}
export function cancel(task) {
return {
type: "CANCEL",
task,
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# redux-persist
此中间件用来对 store 的数据持久化
# 使用
import { PersistGate } from "redux-persist/integration/react";
<PersistGate persistor={persistor} loading={null}>
{/*被包裹的子组件*/}
</PersistGate>
2
3
4
import { persistStore, persistReducer } from "redux-persist";
import storage from "../redux_persist3.0/lib/storage";
const persistConfig = {
key: "root",
storage,
};
const persistedReducer = persistReducer(persistConfig, reducers);
let store = createStore(
persistedReducer,
applyMiddleware(routerMiddleware(history))
);
const persistor = persistStore(store);
export { store, persistor };
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 部分实现
import React, { Component } from "react";
export default class PersistGate extends Component {
componentDidMount() {
// 初始化 localState 和 redux state
this.props.persistor.initState();
}
render() {
return this.props.children;
}
}
2
3
4
5
6
7
8
9
10
11
export default {
setItem(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
getItem(key) {
return JSON.parse(localStorage.getItem(key));
},
};
2
3
4
5
6
7
8
export default (config, reducer) => {
let { storage, key } = config;
key = `persist:${key}`;
let isInit = false;
return (state, action) => {
switch (action.type) {
case "INITSTATE":
isInit = true;
let localState = storage.getItem(key);
localState ? (state = localState) : storage.setItem(key, state);
return state;
default:
if (isInit) {
let newState = reducer(state, action);
storage.setItem(key, newState);
return newState;
}
return reducer(state, action);
}
};
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default function (store) {
return {
...store,
initState() {
store.dispatch({
type: "INITSTATE",
});
},
};
}
2
3
4
5
6
7
8
9
10
# Redux 源码实现
import createStore from "./createStore";
import bindActionsCreators from "./bindActionsCreators";
import combineReducers from "./combineReducers";
export { createStore, bindActionsCreators, combineReducers };
2
3
4
5
import { isActionType } from "./utils";
export default function createStore(reducer, initState) {
if (typeof reducer !== "function")
throw new Error("reducer must be a function type!");
let state = initState;
let subscribeFuncs = [];
//派发
function dispatch(action) {
if (!isActionType(action)) throw new Error("action`s type is error!");
if (action.type === undefined) throw new Error("action`s type is error!");
state = reducer(state, action);
for (let i = 0; i < subscribeFuncs.length; i++) {
const fun = subscribeFuncs[i];
fun();
}
return action;
}
//订阅
function subscribe(func) {
let isSubscribe = true;
subscribeFuncs.push(func);
return function() {
if (isSubscribe) {
// 返回取消订阅函数
let index = subscribeFuncs.indexOf(func);
subscribeFuncs.splice(index, 1);
isSubscribe = false;
}
};
}
function getState() {
return state;
}
dispatch({ type: "9090909" });
return {
dispatch,
subscribe,
getState,
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function combineReducers(reducers) {
return (state, action) => {
let newstate = {};
for (const key in reducers) {
const reducer = reducers[key];
newstate[key] = reducer(state[key], action);
}
return newstate;
};
}
export default combineReducers;
2
3
4
5
6
7
8
9
10
11
let bindActionsCreator = (actionsCreator, dispatch) => {
return function() {
return dispatch(actionsCreator.apply(this, arguments));
};
};
let bindActionsCreators = (actions, dispatch) => {
if (typeof actions === "function") {
return bindActionsCreator(actions, dispatch);
} else {
let bindActions = {};
for (const key in actions) {
const actionsCreator = actions[key];
bindActions[key] = bindActionsCreator(actionsCreator, dispatch);
}
return bindActions;
}
};
export default bindActionsCreators;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 判断是不是 actino 是不是 纯对象
function isActionType(action) {
if (typeof action !== "object" || typeof action === null) return false;
let proto = action;
while (Object.getPrototypeOf(proto)) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(action) === proto;
}
export default isActionType;
2
3
4
5
6
7
8
9
10