Redux源碼解析

寫在開始

本篇主要結合react-native 使用redux的過程,說明使用redux的方法和原理,揭秘Redux單向數(shù)據(jù)流更新的機制原理,適合有一定Redux基礎的同學。

Redux 工作圖

Redux原理圖


上圖是redux的工作原理圖,具體每一步驟的實現(xiàn)請見下面的詳細分析。

Redux需要懂的七大元素

  • combineReducers()
  • createStore()
  • <Provider/>
  • connect()
  • mapStateToProps()
  • mapDispatchToProps()
  • action

一. combineReducers

將應用整體state劃分為不同的reducer,最終合并為rootReducer ===>combineReducers()
1.1reducer

reducer 就是一個方法,接收舊的 state和當前操作 action,返回新的 state。需要注意的是reducer是純函數(shù),永遠不要在reducer里做這些操作:

  • 修改傳入?yún)?shù);
  • 執(zhí)行有副作用的操作,如 API 請求和路由跳轉;
  • 調用非純函數(shù),如Date.now()Math.random()。
1.2combineReducers()用法
const rootReducer = combineReducers({
    localCounter:localCounter,
    serverCounter:serverCounter
})
1.3combineReducers()作用
  • 將子reducer用key-value的形式組成rootReducer,value是該子reducer的實現(xiàn)方法。
  • 返回值:一個總reducer,內部包含所有子reducer
1.4combineReducers()關鍵源碼
function combineReducers(reducers) {
  var reducerKeys = Object.keys(reducers);
  var finalReducers = {};
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];
    ...
    if (typeof reducers[key] === 'function') {
      //reduer數(shù)組
      finalReducers[key] = reducers[key];
    }
  }
   //reducer的key
  var finalReducerKeys = Object.keys(finalReducers);
  ...
  //返回一個function,該方法接收state和action作為參數(shù),其實返回值就是rootReducer
  //遍歷reducers數(shù)組,將action傳入每個reducer方法中得到的新狀態(tài),與舊狀態(tài)對比是否變化,若變化則返回新狀態(tài),若沒有變化則返回舊狀態(tài)
  return function combination() {
   ...
    var hasChanged = false;
    var nextState = {};
    //遍歷reducer數(shù)組,執(zhí)行每個reducer的方法
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i];
      var reducer = finalReducers[key];
      var previousStateForKey = state[key];
      //傳入舊state,action,得到新state
      var nextStateForKey = reducer(previousStateForKey, action);
      ...
      nextState[key] = nextStateForKey;
      //判斷狀態(tài)是否發(fā)生了改變
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    //返回值處理后的state
    return hasChanged ? nextState : state;
  };
}

二. createStore

然后根據(jù) rootReducer創(chuàng)建store===>createStore()
2.1 store

是redux的核心,存儲APP的所有狀態(tài),只能有一個。改變狀態(tài)的唯一方法是調用store.dispatch方法

2.2 createStore()作用
  • 兩種創(chuàng)建方式:
  • createStore(rootReducer,initialState);
  • createStore(rootReducer,initialState,applyMiddleware(thunkMiddleware));

A thunk is a function that wraps an expression to delay its evaluation.簡單來說一個 thunk 就是一個封裝表達式的函數(shù),封裝的目的是延遲執(zhí)行表達式

可以使用第三方庫來增強store,通常會使用redux-thunk庫來支持異步方法的dispatch。thunk最終起的作用,對dispatch調用的action進行檢查,如果action在第一次調用之后返回的是function,則將(dispatch, getState)作為參數(shù)注入到action返回的方法中,執(zhí)行異步邏輯(相當于開始一個新的action),若有返回對象則進行分發(fā)。

  • 返回值

{Store} A Redux store that lets you read the state, dispatch actions
and subscribe to changes.

  • (1) dispatch(action): 用于action的分發(fā),改變store里面的state
  • (2) subscribe(listener): 注冊listenerstore里面state發(fā)生改變后,執(zhí)行該listener。返回unsubscrib()方法,用于注銷當前listener。Redux采用了觀察者模式,store內部維護listener數(shù)組,用于存儲所有通過store.subscribe注冊的listener
  • (3) getState(): 讀取store里面的state
  • (4) replaceReducer(): 替換reducer,改變state修改的邏輯
2.3 源碼分析

function createStore(reducer, preloadedState, enhancer) {
  ...

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.');
    }
    //返回增強store
    return enhancer(createStore)(reducer, preloadedState);
  }

  var currentState = preloadedState;
  var currentListeners = [];
  var nextListeners = currentListeners;
  var isDispatching = false;

  /**
   * 返回當前狀態(tài)
   */
  function getState() {
    return currentState;
  }

  /**
   * 注冊`listener`,維護一個listener的數(shù)組
   * `store`里面`state`發(fā)生改變后,執(zhí)行該`listener`
   * 觀察者模式實現(xiàn)的關鍵
   */
  function subscribe(listener) {
    ...
    nextListeners.push(listener);

    //返回一個注銷listener的方法
    return function unsubscribe() {
      ...
      var index = nextListeners.indexOf(listener);
      nextListeners.splice(index, 1);
    };
  }

  /**
   * Dispatches an action. It is the only way to trigger a state change.
   */
   var currentReducer = reducer;
  function dispatch(action) {
    //類型校驗...
    try {
      isDispatching = true;
      //執(zhí)行rootReducer,得到新的state
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    var listeners = currentListeners = nextListeners;
    //循環(huán)遍歷,執(zhí)行l(wèi)istener,通知數(shù)據(jù)改變了,listeners具體是什么?看容器組件
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]();
    }

    return action;
  }

  /**
   * Replaces the reducer currently used by the store to calculate the state.
   */
  function replaceReducer(nextReducer) {
    currentReducer = nextReducer;
    dispatch({ type: ActionTypes.INIT });
  }

  //返回值
  return _ref2 = {
    dispatch: dispatch,
    subscribe: subscribe,
    getState: getState,
    replaceReducer: replaceReducer
  }, _ref2[_symbolObservable2['default']] = observable, _ref2;
}

三. Provider

store傳遞給應用中的View===> <Provider/>
3.1 <Provider/>是redux提供的組件
<Provider store={store}>
  <APP />
</Provider>
3.2 作用:將store傳遞給其子組件

將store設置到子組件的context中,這樣應用的所有子組件就默認都拿到了store

3.3 源碼
var Provider = function (_Component) {
  _inherits(Provider, _Component);

  //用于指定子組件可直接訪問的上下文數(shù)據(jù),所以子組件可以直接訪問store了
  Provider.prototype.getChildContext = function getChildContext() {
    return { store: this.store, storeSubscription: null };
  };

  function Provider(props, context) {
    _classCallCheck(this, Provider);

    var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));

    _this.store = props.store;
    return _this;
  }

  Provider.prototype.render = function render() {
    return _react.Children.only(this.props.children);
  };

  return Provider;
}(_react.Component);

exports.default = Provider;

四. connect

如何將react中的UI組件與redux的狀態(tài)、事件關聯(lián)起來====>connect()方法
4.0 UI組件和容器組件

React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container component)。

UI組件:

①只負責 UI 的呈現(xiàn),不帶有任何業(yè)務邏輯
②沒有狀態(tài)(即不使用this.state這個變量)
③所有數(shù)據(jù)都由參數(shù)(this.props)提供
④不使用任何 Redux 的 API

容器組件:

①負責管理數(shù)據(jù)和業(yè)務邏輯,不負責 UI 的呈現(xiàn)
②帶有內部狀態(tài)
③使用 Redux 的 API

4.1connect()生成容器組件
  • 通過<Provider/>傳遞store是給容器redux的容器組件
    用于從 UI 組件生成容器組件。connect的意思,就是將這兩種組件連起來。
    connect方法接受兩個參數(shù):mapStateToProps和mapDispatchToProps。它們定義了 UI 組件的業(yè)務邏輯
let ConnectCounter = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter)
4.2 作用

connect是一個高階函數(shù),首先傳入mapStateToProps、mapDispatchToProps,然后返回一個生產Component的函數(shù)wrapWithConnect(),然后再將真正的Component作為參數(shù)傳入wrapWithConnect(MyComponent),這樣就生產出一個經(jīng)過包裹的Connect組件(也就是容器組件)。

容器組件具有如下特點:

  • (1)通過this.context獲取祖先Component的store,也就是通過<Provider/>傳遞過來的store。
  • (2)props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作為props傳給真正的Component,這樣在真正組件中就能通過this.props獲取到各種數(shù)據(jù)和方法。
  • (3)componentDidMount調用store.subscribe(listener)注冊監(jiān)聽方法,對store的變化進行訂閱,當store變化的時候,更新渲染view。
  • (4)componentWillUnmount時注銷訂閱
4.3源碼分析

注意訂閱的實現(xiàn)

var Connect = function (_Component) {
            _inherits(Connect, _Component);

            /*
            * 構造函數(shù)中,構造一個訂閱對象,屬性有this.store,方法this.onStateChange.bind(this)
            */
            function Connect(props, context) {
                ...
                //獲取store。
                //從父組件或context中獲取store。這里使用的是從context中獲取
                //storeKey = _ref$storeKey === undefined ? 'store' : _ref$storeKey,
                _this.store = props[storeKey] || context[storeKey];

                ...

                //初始化訂閱邏輯
                _this.initSubscription();
                return _this;
            }

            //初始化訂閱方法
            Connect.prototype.initSubscription = function initSubscription() {
                if (!shouldHandleStateChanges) return;

                var parentSub = (this.propsMode ? this.props : this.context)[subscriptionKey];

                //wym: 調用的是Subscription.js中方法,向store內部注冊一個listener---this.onStateChange.bind(this)
                this.subscription = new _Subscription2.default(this.store, parentSub, this.onStateChange.bind(this));

            
                this.notifyNestedSubs = this.subscription.notifyNestedSubs.bind(this.subscription);
            };

            //當數(shù)據(jù)狀態(tài)發(fā)生改變時
            Connect.prototype.onStateChange = function onStateChange() {
                this.selector.run(this.props);

                if (!this.selector.shouldComponentUpdate) {
                    this.notifyNestedSubs();
                } else {
                    this.componentDidUpdate = this.notifyNestedSubsOnComponentDidUpdate;
                    //設置state,view會自動重新渲染
                    this.setState(dummyState);
                }
            };

            Connect.prototype.getChildContext = function getChildContext() {
                ...
            };

            /*
            * 組件狀態(tài)完成時,向store注冊監(jiān)聽方法
            */
            Connect.prototype.componentDidMount = function componentDidMount() {
                if (!shouldHandleStateChanges) return;

                //實際調用this.store.subscribe(this.onStateChange);
                //向store注冊監(jiān)聽方法
                this.subscription.trySubscribe();

                this.selector.run(this.props);
                if (this.selector.shouldComponentUpdate) this.forceUpdate();
            };



            Connect.prototype.componentWillUnmount = function componentWillUnmount() {
                //注銷訂閱
                if (this.subscription) this.subscription.tryUnsubscribe();
                this.subscription = null;
                ...
            };


            Connect.prototype.render = function render() {
                var selector = this.selector;
                selector.shouldComponentUpdate = false;

                if (selector.error) {
                    throw selector.error;
                } else {
                    return (0, _react.createElement)(WrappedComponent, this.addExtraProps(selector.props));
                }
            };

            return Connect;
        }(_react.Component);

五.mapStateToProps()

建立一個從(外部的)state對象到(UI 組件的)props對象的映射關系。mapStateToProps會訂閱 Store,每當state更新的時候,就會自動執(zhí)行,重新計算 UI 組件的參數(shù),從而觸發(fā) UI 組件的重新渲染

六. mapDispatchToProps

  • 用來建立 UI 組件的方法到store.dispatch方法的映射,它定義了哪些用戶的操作應該當作 Action,傳給 Store。
  • 它可以是一個函數(shù),也可以是一個對象。(只是不同的寫法,作用一樣)

七. Action

Action 是把數(shù)據(jù)從應用傳到 store 的有效載荷。它是 store 數(shù)據(jù)的唯一來源。一般來說你會通過 store.dispatch() 將 action 傳到 store。
分為:

  • 同步 action ,返回的是一個對象,要求是純凈的函數(shù)。

純凈:沒有特殊情況、沒有副作用,沒有 API 請求、沒有變量修改,單純執(zhí)行計算。

  • 異步action,返回的是一個方法,這個函數(shù)會被Redux Thunk middleware執(zhí)行。這個函數(shù)并不需要保持純凈;它還可以帶有副作用,包括執(zhí)行異步 API 請求。

Redux中的觀察者模式

redux之所以能夠當state變化后,更新綁定的視圖,是因為內部實現(xiàn)的觀察者模式

觀察者模式的實現(xiàn)

1. store提供了注冊監(jiān)聽的方法===>subscribe(listener)
  • store內部維護listener數(shù)組,用于存儲所有通過store.subscrib注冊的listener,store里面state發(fā)生改變(即為調用store.dispatch())后,依次執(zhí)行數(shù)組中的listener。
  • store.subscrib返回unsubscrib方法,用于注銷當前l(fā)istener。

調用store.dispatch()的時候做了兩件事:
(1)更新當前state: currentState = currentReducer(currentState, action);
(2)依次執(zhí)行數(shù)組中的listener。

2. Connect組件中,向store中注冊監(jiān)聽方法
  • ①構造方法中:初始化訂閱邏輯,將listener:this.onStateChange.bind(this)傳遞給Subscription.js。
  • ②componentDidMount方法中,調用store.subscribe(listener)注冊監(jiān)聽方法:onStateChange()

onStateChange方法中,當判斷需要更新數(shù)據(jù)時,調用的是this.setState(state);
===》根據(jù)react機制,則會重新渲染view

  • ③在componentWillUnmount方法中,注銷訂閱

Redux原理圖

根據(jù)以上的源碼分析,redux的工作流可用下圖進行概括。


redux創(chuàng)建過程概括

  • 將一個APP的狀態(tài)分解成不同的reducer,最后創(chuàng)建store(有整個數(shù)據(jù)state,有分發(fā)action的方法,有注冊listener的方法)
  • 將store通過<Provider/>組件傳遞給容器組件
  • 容器組件通過UI組件,mapStateToProps, mapDispatchToProps通過connect()轉化而來
  • 將UI交互事件,外部輸入事件寫成action,用來觸發(fā)reducer

redux更新數(shù)據(jù)過程概括

  • 在UI組件上有交互操作,或外部輸入(網(wǎng)絡請求)時,====>寫成Action
  • store.dispatch(action),結果:
    (1)合成新的state,新的state通過mapStateToProps()傳遞給UI組件,
    (2)執(zhí)行通過store.subscribe()注冊的listener
  • listener具體邏輯:調用setState()設置信息數(shù)值,觸發(fā)View的自動render
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 一、什么情況需要redux? 1、用戶的使用方式復雜 2、不同身份的用戶有不同的使用方式(比如普通用戶和管...
    初晨的筆記閱讀 2,133評論 0 11
  • 做React需要會什么? react的功能其實很單一,主要負責渲染的功能,現(xiàn)有的框架,比如angular是一個大而...
    蒼都閱讀 14,961評論 1 139
  • 前言 本文 有配套視頻,可以酌情觀看。 文中內容因各人理解不同,可能會有所偏差,歡迎朋友們聯(lián)系我討論。 文中所有內...
    珍此良辰閱讀 12,195評論 23 111
  • 上周六參加了一個公司的面試,因為是很長時間以來的第一次面試,發(fā)揮的并不是很好,有兩個下來一想就明白的問題在當時卻卡...
    夏爾先生閱讀 6,486評論 0 15
  • http://gaearon.github.io/redux/index.html ,文檔在 http://rac...
    jacobbubu閱讀 80,422評論 35 198

友情鏈接更多精彩內容