前言
平时使用React做开发的同学对redux都不会陌生,这是一个基于flux架构的十分优秀的状态管理库。这是Redux官方文档对它的描述。
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。
实际上优秀的Redux并不是只作为React的拓展,它可以融合任何框架。本文作为笔者的学习笔记,同样也希望分享给大家。如果错漏,恳请指出。您的批评与指正是我前进路上的一大动力。
基础使用
首先先来介绍它的三个核心概念,分别是action、reducer以及store
action
action是数据的唯一来源,通常使用store.dispatch()将action传入store
- reducer
reducer中一般放入一些逻辑处理,响应action并发送到store
- store
store就是把它们联系到一起的对象。包含了我们熟悉的getState()、dispatch()等方法
更为详尽的资料请阅读官方文档,笔者在这里就不在赘(抄)述(袭)
先来一个计数器
接下来我们先来实现一个计数器,直接上代码吧
app.js
import React, {
Component
} from 'react'
import store from './store'
import { addCount, decCOUNT, decCount } from './actions/Count'
class App extends Component {
constructor(props) {
super(props)
this.state = {
count: null
}
this.add = this.add.bind(this)
this.dec = this.dec.bind(this)
}
componentDidMount() {
console.log(store.getState())
this.setState({
count: store.getState().count
})
}
add() {
store.dispatch(addCount())
this.setState({
count: store.getState().count
})
}
dec() {
store.dispatch(decCount())
this.setState({
count: store.getState().count
})
}
render() {
return (
<div>
{this.state.count}
<button onClick={this.add}>add</button>
<button onClick={this.dec}>dec</button>
</div>
)
}}
export default App这是十分简单的代码,大家看的时候也可以直接略过。
actionTypes.js里面的定义了我们action的类型
actionTypes.js
// ------------------add--------------------
export const ADD_COUNT = 'ADD_COUNT'
export const DEC_COUNT = 'DEC_COUNT'/actions/Count.js里面就是定义我们的action,然后我们把action传到reducer里面去处理
// /actions/Count.js
import {
ADD_COUNT,
DEC_COUNT} from "../actionTypes";
export function addCount() {
return {
type: ADD_COUNT
}}
export function decCount() {
return {
type: DEC_COUNT
}}
// /reducers/Count.js
import {
ADD_COUNT,
DEC_COUNT} from "../actionTypes";
const initialState = {
count: 0
}
export function Count(state = initialState, action) {
const count = state.count
switch (action.type) {
case ADD_COUNT:
return {
count: count + 1
}
case DEC_COUNT:
return {
count: count - 1
}
default:
return state
}}接下来就是store.js
//store.js
import {createStore} from 'redux'
import {Count} from './reducers/Count'
const store = createStore(Count)
export default store这样,一个简单的计数器我们就做好了 看看效果吧。

React-Redux
React-Redux是Redux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据
让我们来看看它怎么使用
//app.js
import React, {Component} from 'react'
import store from './store'
import { connect } from 'react-redux'
import { addCount, decCount } from './actions/Count'
class App extends Component {
constructor(props) {
```
}
render() {
const { count } = this.props
return (
`````
)
}}
function mapStateToProps(state) {
return {
count: state.count
}
}
export default connect(mapStateToProps)(App)
//index.js
ReactDOM.render(<Provider store={store}> <App /> </Provider>, document.getElementById('root'))只要这样就OK啦,编写mapStateToprops函数,可以将store中的state和props关联起来,取值的时候只要 类似于 const { count } = this.props就可以了
createStore
我们创建store的时候是调用了createStore函数,并将一个reducer作为参数传进去,现在我们来看看它的源码吧
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
````
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}可以看到createStore.js中,默认导出的是createStore函数,其接受的参数有三个,然后返回一个对象,也就是说我们创建的store对象中return中的几个方法。我们来一个一个看。
初始判断
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
```
}
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
```
}
if (typeof enhancer !== 'undefined') {
```
}首先它会做一些判断,我们只传入了reducer参数,所以不会走这些逻辑,然后定义一些变量
let currentReducer = reducer
//就是我们传入的reducer
let currentState = preloadedState //undefined
let currentListeners = []
let nextListeners = currentListeners //listener数组,存储subscribe方法传入的函数
let isDispatching = falsegetState()
然后就到了我们熟悉的store.getState()方法,如果在reducer计算的时候调用这个方法,就会报一个错误,。正常使用的话是返回 currentState
function getState() {
if (isDispatching) {
throw new Error(
``` )
}
return currentState
}subcribe()
然后就到了subscribe 方法,这里采用的是一个观察者模式
function subscribe(listener) {
if (typeof listener !== 'function') { ``` }
if (isDispatching) { throw new Error( ``` ) }
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener) //push进观察者数组
return function unsubscribe() {
//移除
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error( ``` )
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}dispatch()
下面就是我们的dispatch方法了,它接收一个action作为参数,利用一开始传入的reducer去计算新的state,并在计算完成后依次调用subscribe方法传入的函数
function dispatch(action) {
if (!isPlainObject(action)) { throw new Error( '``` ) }
if (typeof action.type === 'undefined') { throw new Error( ``` ) }
if (isDispatching) { ``` }
try {
isDispatching = true
currentState = currentReducer(currentState, action) //这里就是调用reducer去计算新的state
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)//计算完之后开始执行观察者数组里面的函数
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}combineReducer
我们不可能所有的逻辑都放在一个reducer里面,要拆分reducer,这时候可以用到combineReducer。
基础用法
//Goods.js
const initialState = {
goods: [{
price: 100,
size: 'M',
id: 0
}, {
price: 200,
size: 'L',
id: 1
}]}
export function Goods(state = initialState, action) {
return state
}//store.js
const rootReducers = combineReducers({
Count,
Goods})
const store = createStore(rootReducers)
这个时候我们发现state被合并成了一个新的state
源码
让我们来解开combineReducer的面纱吧!看combineReducers方法之前,我们先看assertReducerShape方法
assertReducerShape 主要是对传入的reducers做了一层筛选,保证reducers的initialState存在,以及它们的action需要有自己的命名空间
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') { throw new Error( ``` ) }
if (
typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION()
}) === 'undefined' ) {
throw new Error( ``` )
}
}
)
}
接下来就是combineReducers
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
//对reducers的第一层筛选
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
//最后把reducers放在finalReducers数组里
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
//这里就是对reducers的又一层筛选
assertReducerShape(finalReducers)
}
catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') { ``` }
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)
if (typeof nextStateForKey === 'undefined') { ``` }
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}我们可以看到最后它返回了一个combination函数,实际上就是合并过后的store,只要有一个小store(这边我们姑且这么叫,每一个reducer对应一个小store)发生了重新的计算,就会返回一个新的state状态,也就是我们最终得到的store
applyMiddleware
Redux还为我们提供了强大的中间件拓展,让我们来看一下。
基础使用
这里我们以redux-thunk为例,来学习中间件的用法
//store.js
import thunk from 'redux-thunk'
const store = createStore(rootReducers, applyMiddleware(thunk))
//actions/Count.js
export function asyncTest() {
return (dispatch,getState) => {
setTimeout(() => {
dispatch(asyncData())
}, 2000);
}
}
export function asyncData(data) {
return {
type: ASYNC_DATA,
count: -100
}
}
//App.js
componentDidmount(){
store.dispatch(asyncTest()
}我们来看一下效果

源码
然后就是得来看看applyMiddleware干了啥了。
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
}
}
}这个createStore是个啥意思呢,这个时候就要看回我们createStore的源码了。跟没使用中间件的时候不一样。这个时候会走到这个逻辑
return enhancer(createStore)(reducer, preloadedState)
我们使用的时候是这样
import { createStore, combineReducers, applyMiddleware} from 'redux'
const store = createStore(rootReducers //reducer,
//preloadedState=undefined
applyMiddleware(thunk) //这里的返回结果才是真正的enhancer,所以我们要看applyMiddleware返回了什么
)applyMidderware调用是会返回一个函数,我们姑且称他为函数A,函数A接受一个createStore参数,返回一个函数B,函数B可以调用createStore,生成store。所以这里应该是函数B执行,参数是createStore。这个createStore是我们import进来的。这里调用createStore生成一个store,然后对middlewares遍历(我们只传入了thunk),为每一层中间件传入getState和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)))
}compose很短,而且注释直接告诉我们,把传入的函数数组从前往后进行嵌套调用。我们传入compose函数的参数都是形如next=>action=>{}这样的函数,经过compose的处理后,每一个函数的next实则是action=>{}的返回值,执行完之后就将原生的dispatch方法传入,更新state
dispatch = compose(...chain)(store.dispatch)
这个时候我们再来看我们是用的thunk中间件。我们就可以理解了它的_ref里面为什么有dispatch和getState,因为是在applyMiddleware函数中传入的
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
}
}
var thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;如果他的action是一个函数,那么它就会直接终止中间件的调用,直接执行action,更新state
最后
Redux我觉得最难理解的就是它的applyMiddleware方法了,我现在的弟弟水平也只能理解到这种程度。希望以后再回来看的时候会有更深的理解吧!