Redux學習筆記---簡單使用以及源碼閱讀

前言

平時使用React作開發的同窗對redux都不會陌生,這是一個基於flux架構的十分優秀的狀態管理庫。這是Redux官方文檔對它的描述。react

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。可讓你構建一致化的應用,運行於不一樣的環境(客戶端、服務器、原生應用),而且易於測試。

實際上優秀的Redux並非只做爲React的拓展,它能夠融合任何框架。本文做爲筆者的學習筆記,一樣也但願分享給你們。若是錯漏,懇請指出。您的批評與指正是我前進路上的一大動力。redux

基礎使用

首先先來介紹它的三個核心概念,分別是actionreducer以及store數組

  • action 

 action是數據的惟一來源,一般使用store.dispatch()action傳入storebash

  • reducer

reducer中通常放入一些邏輯處理,響應action併發送到store
服務器

  • store

store就是把它們聯繫到一塊兒的對象。包含了咱們熟悉的getState()dispatch()等方法
架構

更爲詳盡的資料請閱讀官方文檔,筆者在這裏就不在贅(抄)述(襲)併發

www.redux.org.cn/
app

先來一個計數器

接下來咱們先來實現一個計數器,直接上代碼吧框架

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複製代碼

這是十分簡單的代碼,你們看的時候也能夠直接略過。async

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-ReduxRedux的官方 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中的stateprops關聯起來,取值的時候只要 相似於 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作了一層篩選,保證reducersinitialState存在,以及它們的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),爲每一層中間件傳入getStatedispatch。而後就執行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裏面爲何有dispatchgetState,由於是在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方法了,我如今的弟弟水平也只能理解到這種程度。但願之後再回來看的時候會有更深的理解吧!

相關文章
相關標籤/搜索