Redux 源碼剖析(JS版)

什麼是Redux?react

Redux 的工做流程(核心思想):git

1.設計全局 state 的數據結構狀態樹
2.設計更改 state 數據、狀態的 actionType 常量
3.根據 actionType,編寫 actionCreator
4.根據各個 actionCreator 的返回值,用 reducer 作數據處理
5.有個 reducer 以後,用 createStore 來獲得全局惟一的 store,來管理 state
6.用 bindActionCreator 函數將 actionCreator 和 store.dispatch 綁定起來,獲得一組能更改 state 的函數
7.分發使用各個狀態修改函數(dispatch)github

源碼剖析

源碼地址redux

源碼結構

src
├── utils                #工具函數文件夾
├── applyMiddleware.js
├── bindActionCreators.js        
├── combineReducers.js     
├── compose.js       
├── createStore.js  
└── index.js             #入口 js
複製代碼

index.js

index.js就是整個代碼的入口:數組

import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'

function isCrushed() {}

if (
  process.env.NODE_ENV !== 'production' &&
  typeof isCrushed.name === 'string' &&
  isCrushed.name !== 'isCrushed'
) {
  warning(
    '。。。'
  )
}

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose
}
複製代碼

這裏的 isCrushed 函數主要是爲了驗證在非生產環境下 redux 是否被壓縮(默認狀況下,isCrushed.name等於isCrushed,若是被壓縮了,函數的名稱會變短,通常會壓縮成數字,那麼 (isCrushed.name !== 'isCrushed') 就是 true),若是被壓縮,就給開發者一個 warn 提示)。bash

而後就是暴露 createStorecombineReducersbindActionCreatorsapplyMiddlewarecompose這幾個接口給開發者使用。數據結構

createStore.js

createStore是redux的核心API。
createStore會生成一個倉庫(store),用來維護一個全局的state閉包

import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'

// 私有 action
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(reducer, preloadedState, enhancer) {
  // 判斷接受的參數個數,來指定 reducer 、 preloadedState 和 enhancer
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  // 若是 enhancer 存在而且適合合法的函數,那麼調用 enhancer,而且終止當前函數執行
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  // 儲存當前的 currentReducer
  var currentReducer = reducer
  // 儲存當前的狀態
  var currentState = preloadedState
  // 儲存當前的監聽函數列表
  var currentListeners = []
  // 儲存下一個監聽函數列表
  var nextListeners = currentListeners
  var isDispatching = false

  // 這個函數能夠根據當前監聽函數的列表生成新的下一個監聽函數列表引用
  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

/*  函數
  ... getState ...

  ... subscribe ...

  ... dispatch ...

  ... replaceReducer ...

  ... observable ...
*/

  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
複製代碼

createStore接收3個參數:app

  • reducer (處理函數,下面介紹)
  • preloadedState(state的初始值)
  • enhancer(一個高階函數,能夠改變store的接口,下面介紹)

createStore的返回值是dispatchsubscribegetStatereplaceReducer[$$observable]: observable,他們共同組成了一個storedom

action

action表明的是用戶的操做。

redux規定action必定要包含一個type屬性,且type屬性也要惟一,相同的type,redux視爲同一種操做,由於處理action的函數reducer只判斷action中的type屬性。

reducer

reducer 只是一個模式匹配的東西,真正處理數據的函數,通常是額外寫在別的地方(固然直接寫在reducer中也沒問題,只是不利於後期維護),只是在reducer中調用罷了。

export default (state, action) => {
    switch (action.type) {
        case A:
        return handleA(state)
        case B:
        return handleB(state)
        case C:
        return handleC(state)
        default:
        return state  // 若是沒有匹配上就直接返回原 state
    }
}
複製代碼

reducer 接收兩個參數,state 以及 action 函數返回的 action對象,並返回最新的 state。

reducer 爲何叫 reducer 呢?由於 action 對象各類各樣,每種對應某個 case ,但最後都彙總到 state 對象中,從多到一,這是一個減小( reduce )的過程,因此完成這個過程的函數叫 reducer。

getState

function getState() {
  return currentState
}
複製代碼

整個項目的currentState是處於一個閉包之中,因此能一直存在,getState會返回當前最新的state。

簡單的說,就是相似於一個get的方法,返回currentState的值。

subscribe

function subscribe(listener) {
  if (typeof listener !== 'function') {
    throw new Error('Expected listener to be a function.')
  }

  let isSubscribed = true

  ensureCanMutateNextListeners()
  nextListeners.push(listener)

  return function unsubscribe() {
    if (!isSubscribed) {
      return
    }

    isSubscribed = false

    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
  }
}
複製代碼

subscribe接收一個listener

他的做用是給store添加監聽函數nextListeners儲存了整個監聽函數列表。
subscribe的返回值是一個unsubscribe,是一個解綁函數,調用該解綁函數,會將已經添加的監聽函數刪除,該監聽函數處於一個閉包之中,會一直存在,因此在解綁函數中能刪除該監聽函數。

dispatch

function dispatch(action) {
  if (!isPlainObject(action)) {
    throw new Error(
      'Actions must be plain objects. ' +
      'Use custom middleware for async actions.'
    )
  }

  if (typeof action.type === 'undefined') {
    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
    currentState = currentReducer(currentState, action)
  } finally {
    isDispatching = false
  }

  const listeners = currentListeners = nextListeners
  for (let i = 0; i < listeners.length; i++) {
    const listener = listeners[i]
    listener()
  }

  return action
}
複製代碼

dispatch接收一個參數action

代碼會先調用createStore傳入的參數reducer方法,reducer接收當前stateaction,經過判斷actionType,來作對應的操做,並返回最新的currentState
dispatch還會觸發整個監聽函數列表,因此最後整個監聽函數列表都會按順序執行一遍。

dispatch返回值就是傳入的action

replaceReducer

function replaceReducer(nextReducer) {
  if (typeof nextReducer !== 'function') {
    throw new Error('Expected the nextReducer to be a function.')
  }

  currentReducer = nextReducer
  dispatch({ type: ActionTypes.INIT })
}
複製代碼

replaceReducer是替換當前的reducer的函數。

replaceReducer接收一個新的reducer,替換完成以後,會執行 dispatch({ type: ActionTypes.INIT }) ,用來初始化store的狀態。

官方舉出了三種replaceReducer的使用場景,分別是:

  • 當你的程序要進行代碼分割的時候
  • 當你要動態的加載不一樣的reducer的時候
  • 當你要實現一個實時reloading機制的時候

combineReducers.js

// 如下只留下了核心代碼
// combination 函數是 combineReducers(reducers) 的返回值,它是真正的 rootReducer
// finalReducers 是 combineReducers(reducers) 的 reducers 對象去掉非函數屬性的產物
 // mapValue 把 finalReducers 對象裏的函數,映射到相同 key 值的新對象中
 
function combination(state = defaultState, action) {
    var finalState = mapValues(finalReducers, (reducer, key) => {
      var newState = reducer(state[key], action); //這裏調用子 reducer 
      if (typeof newState === 'undefined') {
        throw new Error(getErrorMessage(key, action));
      }
      return newState; //返回新的子 state
    });
    //...省略一些無關的代碼
    return finalState; //返回新 state
 };
複製代碼

這個函數能夠組合一組 reducers,而後返回一個新的 reducer。

隨着整個項目愈來愈大,state 狀態樹也會愈來愈龐大,state的層級也會愈來愈深,當某個action.type所對應的 case 要修改深層屬性時,那樣的話函數寫起來就很是難看,因此必須在這個函數的頭部驗證 state 對象有沒有那個屬性。

combineReducers實現方法也比較簡單,它遍歷傳入的reducers,返回一個新的reducer,這個新對象的 key 跟傳入的reducers同樣,它的 value 則是傳入的reducers的不一樣key對應的value展開的{ key: value }

舉個例子:

var reducers = {
    todos: (state, action) { // 此處的 state 參數是全局 state.todos屬性
        switch (action.type) {...} // 返回的 new state 更新到全局 state.todos 屬性中
    },
    activeFilter: (state, action) { // 拿到 state.activeFilter 做爲此處的 state
        switch (action.type) {...} // new state 更新到全局 state.activeFilter 屬性中
    }
}
var rootReducer = combineReducers(reducers)
複製代碼

combineReducers 內部會將 state.todos 屬性做爲 todos: (state, action) 的 state 參數傳進去,經過 switch (action.type) 以後返回的 new state 也會更新到 state.todos 屬性中;也會將 state.activeFilter 屬性做爲 activeFilter: (state, action) 的 state 參數傳進去,經過 switch (action.type) 以後返回的 new state 也會更新到 state.activeFilter 屬性中。

bindActionCreators.js

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    }
  }
  return boundActionCreators
}
複製代碼

bindActionCreators的代碼就是將actionCreatordispatch聯結在一塊兒。

對於多個 actionCreator,咱們能夠像reducers同樣,組織成一個 key/action的組合。
因爲不少狀況下,actionactionCreator 返回的,實際上要這樣調用 store.dispatch(actionCreator(...args))很麻煩,只能再封裝一層,經過反覆組合,將嵌套的函數分離。

compose.js

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調用了ES5的Array.prototype.reduce方法,將形如fn(arg1)(arg2)(arg3)...的柯里化函數按照順序執行。

其傳入的參數爲函數數組,返回的爲reduce從左到右合併後的新的函數,是一個相似於鏈式調用的過程。

funcs.reduce((a, b) => (...args) => a(b(...args)))
複製代碼

這句特別重要,組合函數的這部很是重要,咱們發現...args參數會依次的從右到左執行,好比將b(...args)的執行結果,傳入a中做爲參數繼續執行。

applyMiddleware.js

export default function applyMiddleware(...middlewares) {
  return createStore => (reducer, initialState) => {
    var store = createStore(reducer, initialState);
    var dispatch = store.dispatch; //拿到真正的 dispatch
    // 將最重要的兩個方法 getState/dispatch 整合出來
    var middlewareAPI = {
      getState: store.getState,
      dispatch: action => dispatch(action)
    };
    // 依次傳遞給 middleware,讓它們有控制權
    var chain = middlewares.map(middleware => middleware(middlewareAPI));
    dispatch = compose(...chain, dispatch); // 再組合出新的 dispatch

    return {
      ...store,
      dispatch
    };
  };
}
複製代碼

applyMiddleware就是中間件的意思。

applyMiddleware接收中間件爲參數,並返回一個以createStore爲參數的函數;

同時applyMiddleware又是createStore函數中的第三個參數,因此咱們回到createStore的代碼,找到了:

if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }

  return enhancer(createStore)(reducer, preloadedState)
}
複製代碼

createStore中傳了第三個參數的時候,會執行enhancer(createStore)(reducer, preloadedState),這是一個柯里化函數;

咱們能夠設想中間件的使用方法:const store = createStore( reducer, applyMiddleware([...中間件]))
applyMiddleware([...中間件])的返回值是一個以createStore爲參數的函數,這個函數會在createStore中執行,返回的函數也會繼續執行,最後返回一個store

繼續回到applyMiddleware中,在返回store以前,中間件將最重要的兩個方法 getState/dispatch 整合出來,並傳遞給中間件使用,中間件處理完以後,返回一個新的dispatch

applyMiddleware把中間件放在一個chain數組中,並經過compose方法(咱們上面已經介紹過了),讓每一箇中間件按照順序一次傳入dispatch參數執行,再組合出新的 dispatch。因而可知,每一箇中間件的格式都應該是接收一個{ dispatch, getState },返回一個(dispatch) => { return function(action) { ... }}

補充(結合React使用)

一般咱們使用Redux的時候,都是React要使用
那麼就避免不了把這二者聯繫起來,其實本質上ReduxReact是沒有任何聯繫的。

那麼咱們要用到依賴react-redux

react-redux

就是把 react 和 redux 聯繫到一塊兒。

react-redux提供兩個方法:connectProvider

connect

connect方法就是鏈接React組件和Redux store

connect其實是一個高階函數,返回一個新的已經與 Redux store 鏈接的組件類。

例如:

const VisibleCounter = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)
複製代碼

Counter 是 UI 組件,VisibleCounter就是由 react-redux 經過connect方法自動生成的容器組件。

  1. mapStateToProps:從Redux狀態樹中提取須要的部分做爲props傳遞給當前的組件。
  2. mapDispatchToProps:將須要綁定的響應事件(action)做爲props傳遞到組件上(也能夠不用寫這個,直接把actions綁上去就行了)。
    例如:
//須要引入actions,就是一系列的方法
const VisibleCounter = connect(
  mapStateToProps,
  actions
)(Counter)
複製代碼

代碼示例:

import React, { Component } from 'react';
import { connect } from 'react-redux'
import actions from '../store/actions/counter1'

class Counter1 extends Component {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <>
                <h1>Counter1</h1>
                <p>{this.props.number}</p>
                <button onClick={() => this.props.increment(2)}>+</button>
                <button onClick={() => this.props.decrement(2)}>-</button>
            </>
        )
    }
}

// state 就是倉庫中的狀態
// function mapStateToProps(state){
//     // number第1個number是指這個組件中的屬性
//     // state.number 指的是倉庫中的number
//     return {number:state.number}
// }

// let mapStateToProps = function(state){
//     // number第1個number是指這個組件中的屬性
//     // state.number 指的是倉庫中的number
//     return {number:state.number}
// }

// ({number:state.number})  若是返回一個對象,須要給穿上對象外面包一個小括號
// let mapStateToProps = state=>({number:state.number})

//第一種寫法------------------------------------------------
// function mapStateToProps(state) {
//     return {
//         number: state.counter1.number
//     }
// }

// function mapDispatchToProps(dispatch) {
//     return bindActionCreators(actions, dispatch)
// }

// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);

//第二種寫法------------------------------------------------
// let mapStateToProps = state=>({number:state.counter1.number})

// let mapDispatchToProps = dispatch=>bindActionCreators(actions,dispatch)

// export default connect(mapStateToProps,mapDispatchToProps)(Counter1);

//第三種寫法------------------------------------------------
let mapStateToProps = state=>({number:state.counter1.number})

export default connect(mapStateToProps,actions)(Counter1);
複製代碼

其中引入的actions就是一系列的方法:

import * as types from "../action-types"

function increment(payload) {
    return { type: types.ADD1, payload }
}
function decrement(payload) {
    return { type: types.SUB1, payload }
}

export default { increment, decrement }
複製代碼

Provider

Provider能夠實現store的全局訪問,將store傳給每一個組件。

原理:使用Reactcontextcontext能夠實現跨組件之間的傳遞。

使用時,咱們須要在入口index.js文件中引入使用,以下:

import React from "react"
import ReactDOM from "react-dom"
import App from './App'
//使用react-redux中的Provider
import { Provider } from 'react-redux'

import store from './store'

ReactDOM.render(
    // 別忘了包起來,而且把store綁上去,可讓全部的組件均可以使用倉庫裏的狀態
    <Provider store={store}>
        <App></App>
    </Provider>
    , window.app)
複製代碼

而且咱們須要使用其中的connect方法實現數據和方法的映射,使用方式也很是簡單

redux-thunk

就是加強store.dispatch()的功能,便可以在reducer中進行一些異步的操做,能夠派發多種類型的東西

使用方式也很是簡單,直接將thunk中間件引入,放在applyMiddleware方法之中便可。

redux-logger

能夠輸出日誌信息

使用的時候須要經過createLogger建立一個logger,再把建立這個logger放在applyMiddleware方法之中便可。

import { createLogger } from 'redux-logger'

const logger = createLogger({
    // ...options
})

applyMiddleware(logger)
複製代碼

等等......


TOT

相關文章
相關標籤/搜索