redux 流程图
redux 的使用
先看一下目录结构
components/counter.js:创建store
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import reducers from '../reducers';
const store = createStore(reducers, applyMiddleware(thunk))
export default store;
reducers/counter.js:构建组件初始状态;创建纯函数reducer;接收action,返回全新的state。
const initialState = {
num: 0
}
const reducer = (state = initialState, action) => {
const { type, data } = action;
switch (type) {
case 'increment':
return {
num: state.num + data
}
case 'decrement':
return {
num: state.num - data
}
default:
return state;
}
}
export default reducer;
reducers/index.js:组合多个 reducer,通过 action.type 将 action 分发到具体的reducer中处理
import { combineReducers } from 'redux';
import counter from './counter';
export default combineReducers({
counter
})
actions/counter.js:actionCreator,产出 action 的地方;返回值为一个拥有 type 字段的 object对象,或者是一个函数(需要加入redux-thunk 中间件予以支持,后面会讲到)
import { bindActionCreators } from 'redux';
import store from '../store';
const actionCreator = {
increment: () => {
return {
type: 'increment',
data: 1
}
},
decrement: () => {
return (dispatch, getState) => {
setTimeout(() => {
dispatch({
type: 'decrement',
data: 2
})
}, 1000)
}
},
reset: () => {
return {
type: 'reset'
}
}
}
actionCreator.reset = bindActionCreators(actionCreator.reset, store.dispatch);
export default actionCreator
components/counter.js:view层,执行 dispatch -> 触发 action -> action 经过相应的 reducer 处理后,返回全新的 state -> 执行 store.subscribe 注册的监听函数 -> 监听函数中 setState 更新状态 -> 触发 react 更新机制 -> 组件更新
import React from 'react';
import store from '../store';
import actionCreator from '../actions/counter'
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
num: store.getState().counter.num
}
store.subscribe(() => {
const state = store.getState();
this.setState({
num: state.counter.num
})
})
}
increment = () => {
store.dispatch(actionCreator.increment())
}
decrement = () => {
store.dispatch(actionCreator.decrement())
}
reset = () => {
actionCreator.reset()
}
render() {
const { num } = this.state;
return (
<div className="App">
{
num
}
<div></div>
<button onClick={ this.increment }>+</button>
<button onClick={ this.decrement }>-</button>
<button onClick={ this.reset }>reset</button>
</div>
);
}
}
export default Counter;
redux 原理解析
整个过程用到了几个重要的方法:
- createStore:创建 store
- store.dispatch:触发 action
- store.subscribe:注册监听函数,dispatch中会调用所有注册的监听函数
- store.getState:返回当前 state
- combineReducers:组合多个 reducer 成一个,根据 action.type 分发 action 到对应的 reducer 中处理
- bindActionCreators:用 dispatch 封装 actionCreator,这样在组件中直接调用 actionCreator 方法就能执行 dispatch,而不需要在组件中显示的调用 dispatch
- applyMiddleware:利用中间件,增强 dispatch 的功能
下面分别大致说一下每个方法的实现过程
createStore
createStore 接收三个参数 (reducer, preloadedState, enhancer),reducer 好理解,preloadedState 是初始状态,很少用到,enhancer 是增强器,用来增强 redux。
上面的代码中我是这样写的 createStore(reducers, applyMiddleware(thunk)),enhancer写在了第二个参数上,第三个参数没有传,之所以能生效是因为下面这个判断
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}
常见的增强器有 redux-thunk、redux-saga,需要使用 applyMiddleWare 处理之后才是合格的 enhancer。
该函数内部定义了这么几个重要的变量
let currentState = preloadedState //从函数createStore第二个参数preloadedState获得
let currentReducer = reducer //从函数createStore第一个参数reducer获得
let currentListeners = [] //当前订阅者列表
let nextListeners = currentListeners //新的订阅者列表
let isDispatching = false
createStore 执行完会返回这么几个东西:
{
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
dispatch
if (!isPlainObject(action)) {
// dispatch 的入参必须是一个简单的 Object 对象
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
// 并且这个 Object 需要具有 type 属性
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true // 可以理解为线程锁,防止两个action同时执行
currentState = currentReducer(currentState, action) // 得到最新的state
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener() // 执行所有的监听函数
}
subscribe
nextListeners.push(listener)
// 返回解绑函数
return function unsubscribe() {
...
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
getState
return currentState as S
combineReducers
它的作用是用来合并多个 reducer,最终会返回一个函数代替所有的 reducer。在 dispatch 后,这个合并后的 combination 会遍历所有的 reducer 并执行它们,拿到最新的 state。然后与原来的 state 对比看是否是同一个引用,如果是就返回老的 state,否则返回新的 state。
先将入参 reducers 拷贝到 finalReducers 中
const finalReducerKeys = Object.keys(finalReducers)
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
bindActionCreators
经过该方法封装后的 actionCreator 具备了 dispatch 能力,所以 actionCreator 直接调用就可以触发 action,不用在组件中显示的调用 dispatch。如 actions/counter.js 中的 reset,在组件中直接调用 counter.reset() 就可以触发 dispatch,而不用这么写 store.dispatch(counter.reset())
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
return function(this: any, ...args: any[]) {
return dispatch(actionCreator.apply(this, args))
}
}
export default function bindActionCreators(
actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
dispatch: Dispatch
) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
/***********容错**********/
const boundActionCreators: ActionCreatorsMapObject = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
applyMiddleware
在 dispatch 函数中我们提到,它的入参必须是一个简单的 Object 对象,而且还需要有 type 属性。如 actions/counter.js 中的 increment,它的返回值就是带有 type 字段的 Object 对象。
而 decrement 这个 actionCreator 的返回值却是一个函数,最后也顺利的触发了 action,更新了试图。这是为什么呢?如果依据 dispatch 的源码来看,这肯定会报错才对啊?
原因就是我们使用了 redux-thunk 这个中间件增强了 dispatch 的功能,使其具备了接收函数作为入参的能力。
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
- 增强器 enhancer 是 applyMiddleware 方法的返回值,这个 enhancer 主要分为一下几步:
- 入参是 createStore,通过它创建一个 store
- 定义 middlewareAPI,所有的中间件都需要实现 getState、dispatch 这两个方法
- middlewares 调用 Array.prototype.map 进行改造,存放在 chain
- 用compose整合chain数组,并赋值给dispatch
- 将新的dispatch替换原先的store.dispatch
这样看还有有些懵逼,我们来看一下大名鼎鼎的 redux-thunk 是怎么实现的。看懂了它再回头去看 applyMiddleware 就豁然开朗了
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
我们最终得到的 thunk 应该是这个样子的
const thunk = ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
它的入参是一个简单 Object,且拥有 dispatch 和 getState 两个属性。我们现在再把这个 thunk 带回到 applyMiddleware 方法中的 chain 那一步
const chain = middlewares.map(middleware => middleware(middlewareAPI))
执行 thunk 可以得到如下结果,是一个函数,它的入参是 next。返回值也是一个函数,入参是 action。为了方便理解,我们假设使用了两个中间件
// thunk(middlewareAPI)
const result1 = next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
const result2 = 同上
// 那么上一步的 chain 就是这个样子
chain = [result1, result2]
现在我们得到了 chain 的模样,接着把 chain 带入到 applyMiddleware 中
dispatch = compose(...chain)(store.dispatch)
compose 函数是用来组合多个函数的,把一个函数的执行结果作为下一个函数的入参。我们来看下源码
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
那么 chain 经过 compose 处理之后得到的应该是一个函数
const composeRst = (args) => result1(result2(args))
走到这里,我们再回头看 result1 的代码,就能够知道它的入参 next 其实就是下一个中间件。我们接着往下执行
dispatch = compose(...chain)(store.dispatch)
等价于
dispatch = composeRst(store.dispatch)
等价于
dispatch = result1(result2(store.dispatch))
等价于
dispatch = action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return result2(action);
}
也就是说经过 compose(...chain)(store.dispatch) 一顿操作猛如虎,最终得到的其实就是一个函数,这个函数的入参是一个 action。这不就是 dispatch 的功能吗!!!
但是比原始的 dispatch 多了个 if 判断,action 原本只能是一个带有 type 属性的简单 Object,现在可以传 function 了,然后把 dispatch、getState 传入了这个 function。
利用这一点我们就可以在 actionCreator 中返回一个异步函数去后端获取数据,然后在异步函数的回调中调用 dispatch 更新状态,如文章开头 actions/counter.js 中的 decrement 这个 actionCreator,是不是觉得很巧妙。鼎鼎大名的 redux-thunk 其实就那么几行代码。
整个 redux 的使用及原理讲完了,但是代码这么写一点也不够优雅。通常在 react 项目中使用 redux,我们会借用 react-redux 做为桥梁,更加优雅的使用 redux。下一篇讲解 react-redux 的使用和原理。