Redux源碼簡析(版本3.5.2)

爲何要寫這篇文章?html

之前想寫來着,發現有太多這種文章,就不想去總結了,結果太長時間沒作web開發了,就忘記了,在阿里面試被問了好幾回,回答的都不很理想,因此仍是那句話好記性不如爛筆頭,更況且尚未好記性!react

先看一個示例:git

import React,{Component,PropTypes} from 'react'
import ReactDOM from  'react-dom'
import {createStore } from 'redux'
import {Provider,connect }from 'react-redux'

// React component
class Counter extends Component {
    render() {
        const {value,onIncreaseClick} = this.props
        return ( 
           <div>
              <span>{value} </span>
              <button onClick={onIncreaseClick}>Increase</button> 
           </div>
        )
     }
   }
     const increaseAction = {
            type: 'increase'
        }

        function counter(state = {
            count: 0
        },
        action) {
            const count = state.count
            switch (action.type) {
            case 'increase':
                return {
                    count:
                    count + 1
                }
            default:
                return state
            }
        }

        const store = createStore(counter)
        function mapStateToProps(state) {
            return {
                value: state.count
            }
        }
        function mapDispatchToProps(dispatch) {
            return {
                onIncreaseClick: () = >dispatch(increaseAction)
            }
        }
        const App = connect(mapStateToProps, mapDispatchToProps)(Counter)

        ReactDOM.render( 
         <Provider store = {store}>
          <App/>
         </Provider>,
       document.getElementById('root'))複製代碼

這是Redux在react中的應用,其實redux和react是沒有關係的,redux能夠在其餘任何框架中使用github

這裏只是經過react-redux將react和redux結合在一塊兒了!web

接下來咱們先拋棄react咱們來分析源碼:面試

createStore源碼

function createStore(reducer, preloadedState, enhancer) {
    let currentReducer = reducer 
    let currentState = preloadedState 
    let currentListeners = []
    let nextListeners = currentListeners
    function getState() {
        return currentState
    }
    function subscribe(listener) {
        nextListeners.push(listener)
    }
    function dispatch(action) {
        currentState = currentReducer(currentState, action) const listeners = currentListeners = nextListeners
        for (let i = 0; i < listeners.length; i++) {
            const listener = listeners[i] listener()
        }
        return action
    }
    function replaceReducer(nextReducer) {
        currentReducer = nextReducer dispatch({
            type: ActionTypes.INIT
        })
    }
    dispatch({
        type: ActionTypes.INIT
    }) 
  return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
    }
}複製代碼

簡化後的代碼能夠看到createStore接受三個參數,後面兩個參數先忽略,也就是createStore接受傳入一個reducerredux

返回:設計模式

return {    
    dispatch,
    subscribe,
    getState,
    replaceReducer,
  }複製代碼

能夠很容易看到返回的store就是一個訂閱發佈設計模式bash

  • dispatch: 用於action的分發,改變store裏面的state
  • subscribe: 註冊listener,store裏面state發生改變後,執行該listener
  • getState: 讀取store裏面的state閉包

  • replaceReducer: 替換reducer,改變state修改的邏輯

subscribe傳入function訂閱

dispatch(action)發佈消息,將action和當前的state傳入定義好的reducer獲得新的state

接着通知以前經過store.subscribe訂閱消息的函數,這樣看是否是特別簡單


// 先寫一個,成爲reducer
function count (state, action) {
    state=state || 2020;
    switch (action.type) {
        case 'add':
            return {
                year: state.year + 1
            };
        case 'sub':
            return {
                year: state.year - 1
            }
        default :
            return state;
    }
}
var store = createStore(count);
// store裏面的數據發生改變時,觸發的回調函數
store.subscribe(function () {
      console.log('the year is: ', store.getState().year);
});

var action = { type: 'add' };
// 改變store裏面的方法
store.dispatch(action); // 'the year is: 2021複製代碼



很顯然,只有一個 reducer 是 hold 不住咱們整個應用中全部 action 操做的, 會變得很難維護

怎麼辦呢,這時候要寫多個reducer,就用到了

使用combineReducers

var reducer_0 = function(state = {},action) {
    switch (action.type) {
    case 'SAY_SOMETHING':
        return {...state,
            message: action.value
        }
    default:
        return state;
    }
}

var reducer_1 = function(state = {},action) {
    switch (action.type) {
      case 'SAY_SOMETHING':
        return {...state,
            message: action.value
        }
      case 'DO_SOMETHING':
        // ...        
      case 'LEARN_SOMETHING':
        // ...        
      default:
        return state;
    }
}複製代碼

和並多個reducer,使用combineReducers直接搞定

import {
    createStore,
    combineReducers
}
from 'redux'
var reducer = combineReducers({
    user: reducer_0,
    items: reducer_1
})複製代碼

接下來看下combineReducers是怎麼作到的

function combineReducers(reducers) {
    const reducerKeys = Object.keys(reducers)
    const finalReducers = {}

    for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]
        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
        }
    }

    const finalReducerKeys = Object.keys(finalReducers)

    return function combination(state = {},
    action) {
        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
    }
}複製代碼

combineReducers方法將多個子reducers合併爲一個對象finalReducers

當dispatch(action)觸發合併後的combination,combination經過key在調用各個子模塊,返回state,最後合併爲最新的nextState,是否是很簡單

看到這裏可能會有疑問,dispatch(action)後就是觸發reducers,那異步請求怎麼辦呢?

那就想辦法唄,既然reducer是獲取更新State,異步請求是獲取最新的數據,那隻能在reducer以前加一層!

// 一般來講中間件是在某個應用中 A 和 B 部分中間的那一塊,
//  dispatch(action)-----> reducers
// 變成:
// dispatch(action) ---> middleware 1 ---> middleware 2 ---> middleware 3 ...--> reducers

中間件middleware

要發送異步請求就須要中間件,先看一個最簡單的中間件redux-thunk.

// 咱們爲異步 action creator 提供的中間件叫 thunk middleware// 它的代碼在:https://github.com/gaearon/redux-thunk.

var thunkMiddleware = function({dispatch,getState}) {
    return function(next) {
        return function(action) {
            return typeof action === 'function' ? action(dispatch, getState) : next(action)
        }
    }複製代碼

 如上所述,中間件由三個嵌套的函數構成(會依次調用):
  1. 第一層向其他兩層提供分發函數和 getState 函數(由於你的中間件或 action creator 可能須要從 state 中讀取數據)
  2. 第二層提供 next 函數,它容許你顯式的將處理過的輸入傳遞給下一個中間件或 Redux(這樣 Redux 才能調用全部 reducer)。
  3. 第三層提供從上一個中間件或從 dispatch 傳遞來的 action,
這個 action 能夠調用下一個中間件(讓 action 繼續流動) 或者  以想要的方式處理 action。


代碼很是簡單,來結合一個例子看看中間件怎麼使用:

import {
    createStore,
    applyMiddleware
} from 'redux'

//將redux-thunk代碼直接放在這裏方便閱讀
var thunkMiddleware = function({dispatch,getState}) {
    return function(next) {
        return function(action) {
            return typeof action === 'function' ? action(dispatch, getState) : next(action)
        }
    }
}

var middleware = applyMiddleware(thunkMiddleware) 
var reducer = function(state = {},action) {
    switch (action.type) {
    case 'SAY':
        return Object.assign(state, {
            message: action.value
        })
    default:
        return state
    }
}

const store = createStore(reducer, undefined, middleware)

// 如今 store 的 middleware 已經準備好了,再來嘗試分發咱們的異步 action:
var asyncSayAction = function(message) {
    return function(dispatch) {
        setTimeout(function() {
            console.log(new Date(), 'Dispatch action now:') dispatch({
                type: 'SAY',
                message
            })
        },
        2000)
    }
}

store.dispatch(asyncSayAction('Hi'));複製代碼

// dispatch(action) ---> middleware 1 ---> middleware 2 ---> middleware 3 ...--> reducers

由上圖可知dispatch是要先通過中間件的,以前提交dispatch(action)都是對象{type:'SAY'},

redux-thunk是經過提交action判讀是否是function,決定下一步操做

接下來分析中間件是怎麼加入redux的

applyMiddleware

function applyMiddleware(...middlewares) {
    return function(createStore) {

        return function(reducer, preloadedState, enhancer) {

            const store = createStore(reducer, preloadedState, enhancer) 
            let dispatch = store.dispatch 
            let chain = []
            const middlewareAPI = {
                getState: store.getState,
                dispatch: (action) = >dispatch(action)
            }
            chain = middlewares.map(middleware = >middleware(middlewareAPI)) 
            dispatch = compose(...chain)(store.dispatch)
            return {
                ...store,
                dispatch
            }
        }
    }
}複製代碼



根據createStore的傳入參數調用enhancer(createStore)(reducer, preloadedState)

即applyMiddleware返回的函數

function(createStore) {

        return function(reducer, preloadedState, enhancer) {

            const store = createStore(reducer, preloadedState, enhancer) 
            let dispatch = store.dispatch 
            let chain = []
            const middlewareAPI = {
                getState: store.getState,
                dispatch: (action) = >dispatch(action)
            }
            chain = middlewares.map(middleware = >middleware(middlewareAPI)) 
            dispatch = compose(...chain)(store.dispatch)
            return {
                ...store,
                dispatch
            }
        }
}複製代碼

這個函數先建立store,跟沒有中間件的流程同樣,接着看是怎麼把中間件加入到

經過chain = middlewares.map(middleware => middleware(middlewareAPI))

傳入第一層提供分發函數和 getState 函數(由於你的中間件或 action creator 可能須要從 state 中讀取數據)

仍是看redux-thunk

var thunkMiddleware = function({dispatch,getState}) {
    return function(next) {
        return function(action) {
            return typeof action === 'function' ? action(dispatch, getState) : next(action)
        }
    }
    }
複製代碼

接着經過 dispatch = compose(...chain)(store.dispatch)

組裝中間件,返回的是一個包裝函數,最好本身打斷點調試看一下,

返回的包裝函數爲dispatch,當咱們再次使用dispatch提交的時候就會先調用中間件,

中間件中的next表明下一個中間件,一直到最後一箇中間件結束,調用的next是store.dispatch

而後出發reducer

 dispatch(action) ---> middleware 1 ---> middleware 2(第一個next) ---> middleware 3(第二個next) ...store.dispatch(最後一個next) --> reducer



打斷點能夠清楚的看出上面redux-thunk中間件

因爲這裏只用到一箇中間件redux-thunk,因此next直接是包裝前dispatch,調用後直接觸發reducer

到這裏redux就結束了,是否是很簡單,中間件的加入就是對原有dispatch的再包裝,包裝的代碼有點難懂,要好好的理解閉包和高階函數打斷點才能看懂喲!!!

接下來看redux是怎麼和react結合的:

基本使用

目前已經有現成的工具react-redux來實現兩者的結合:

react-redux提供了Provider  connect兩個工具

Provider -- 提供store

React經過Context屬性,屬性(props)直接給子孫component,無須經過props層層傳遞, Provider僅僅起到得到store,而後將其傳遞給子孫元素而已:

export default class Provider extends Component {  getChildContext() {    return { store: this.store }  }  constructor(props, context) {    super(props, context)    this.store = props.store  }  render() {    return Children.only(this.props.children)  }}複製代碼

是否是很簡單!

接下來看看複雜的connect

connect是一個高階函數,首先傳入mapStateToProps、mapDispatchToProps,而後返回一個生產Component的函數(wrapWithConnect)

看看connect是怎麼實現高階組件,更新數據的

function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
    const mapState = mapStateToProps let mapDispatch = mapDispatchToProps

    return function wrapWithConnect(WrappedComponent) {
        class Connect extends Component {
    
            constructor(props, context) {
                super(props, context) 
                this.version = version 
                this.store = props.store || context.store 
                const storeState = this.store.getState() 
                this.state = {
                    storeState
                }
            }
            trySubscribe() {
                if (shouldSubscribe && !this.unsubscribe) {
                    this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
                }
            }
            componentDidMount() {
                this.trySubscribe()
            }

            componentWillReceiveProps(nextProps) {
                if (!pure || !shallowEqual(nextProps, this.props)) {
                    this.haveOwnPropsChanged = true
                }
            }


            handleChange() {
                const storeState = this.store.getState() this.setState({
                    storeState
                })
            }
            render() {
                this.renderedElement = createElement(WrappedComponent, this.mergedProps)
                return this.renderedElement
            }
        }

        Connect.displayName = connectDisplayName Connect.WrappedComponent = WrappedComponent Connect.contextTypes = {
            store: storeShape
        }
        Connect.propTypes = {
            store: storeShape
        }

        return hoistStatics(Connect, WrappedComponent)
    }
}複製代碼


將connect代碼簡化後能夠看到,HOC高階組件就是對自定義組件的封裝

封裝後的組件,經過獲取redux的store,而後經過this.store.subscribe監聽數據變化

trySubscribe() {
                if (shouldSubscribe && !this.unsubscribe) {
                    this.unsubscribe = this.store.subscribe(this.handleChange.bind(this)) this.handleChange()
                }
            }複製代碼

一但數據有變化觸發handleChange

調用setState觸發render,在render中獲取須要的參數合併傳遞給自定義組件完成更新:

handleChange() {
                const storeState = this.store.getState() this.setState({
                    storeState
                })
            }複製代碼

到這裏就接近尾聲了,只要理解原理,之後再使用是否是就很容易上手1

最後從網上搞了張圖,很是好!

相關文章
相關標籤/搜索