前言

平时使用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()等方法

更为详尽的资料请阅读官方文档,笔者在这里就不在赘(抄)述(袭)

www.redux.org.cn/

先来一个计数器

接下来我们先来实现一个计数器,直接上代码吧

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 = false复制代码

getState()

然后就到了我们熟悉的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方法了,我现在的弟弟水平也只能理解到这种程度。希望以后再回来看的时候会有更深的理解吧!