Redux 源碼剖析

Redux

Redux 是為了處理應(yīng)用復(fù)雜狀態(tài)流而設(shè)計的狀態(tài)管理庫囚聚,它吸收了 Flux 架構(gòu)和函數(shù)式編程的優(yōu)秀思想,提出了應(yīng)用分層設(shè)計的解決方法蓖谢;

Redux 基本架構(gòu)

Redux 的將應(yīng)用分為 Actions捂蕴、State 和 View 三層譬涡;

Actions

Actions 描述用戶操作的基本信息,包括操作類型和所需傳遞的數(shù)據(jù)啥辨;

在代碼層面看涡匀,一個 action 就是一個對象,實際編碼過程中會將 action 設(shè)計為 action creator溉知,里面直接封裝 action.type陨瘩,只需要傳遞數(shù)據(jù)。

// addTodoAction
export var addToDo = payload => ({
    type: 'ADD_TODO',
    payload,
});

Reducers

Reducer 是根據(jù) action 類型生成新的 state 的函數(shù)着倾。這里要求 state 是個 Immutable 對象拾酝,因為為了降低性能開銷,新舊 state 將采用淺比較卡者,使用 Immutable 對象可以很好匹配這一適用場景蒿囤。

// todosReducer
var todos = (state = [], action) => {
    switch(action.type) {
        case 'ADD_TODO':
            return [...state, {id: action.id, payload: action.payload}];
        default:
            return state;
    }
}

export default todos;

Store

Store 是存儲整個 state 樹的倉庫,實際上就是一個對象崇决,里面部署了 dispatch() 和 getState() 等主要方法材诽。

import { createStore, combineReducers } from 'redux';
import todosReducer from 'reducers/todosReducer';
import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';

var rootReducer = combineReducers({
    todos: todosReducer,
});

var store = createStore(rootReducer);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

View

View 就是視圖層,可以對用戶交互給予反饋恒傻;

import React from 'react';
import {connect} from 'react-redux';
import * as TodoAction from 'actions/todoAction';

var AddToDo = props => {
    const {
        todos,
        dispatch,
    } = props;

    return <div>
        <section className='todo-list'>
            {
                todos.map(todo => <p key={todo.id}>{todo.name}</p>)
            }
        </section>
        <button onClick={() => dispatch(TodoAction({name: 'hello world'})}>add to do</button>
    </div>;
}

var mapStateToProps = state => ({
    todos: state.todos,
});

export default connect(mapStateToProps)(AddToDo);

[圖片上傳失敗...(image-1b3958-1616590677926)]

數(shù)據(jù)流向

這里以 React 這一 UI 框架為例脸侥,講解一下 React + Redux 的基本數(shù)據(jù)流向;

  • 首先盈厘,UI 從 Store 里面獲取 State睁枕,這里通過 react-redux 的 Provider 組件實現(xiàn) store 的注入;
  • UI 發(fā)生交互后沸手,會調(diào)用 dispatch(action(payload)) 方法外遇,dispatch 方法默認掛載在 store 上;
  • dispatch 觸發(fā)后契吉,會調(diào)用 rootReducer()跳仿,rootReducer 會根據(jù)之前的 state 和 action 計算新的 state;
  • 新的 state 會重新從根組件傳遞下去捐晶,如果 state 發(fā)生變化菲语,則 re-rerender 對應(yīng)的組件,從而實現(xiàn)視圖的更新惑灵;

源碼解析

源碼以 redux 和 react-redux 為內(nèi)容山上,為了避免干擾,將會在源碼基礎(chǔ)上去除本身邊界條件英支、狀態(tài)鎖以及干擾分析部分的代碼胶哲,并進行簡化;

combineReducer

combineReducer 是一個高階函數(shù)潭辈, 作用就是將所有的子 reducer 合并為一個根 reducer鸯屿,當(dāng)調(diào)用 rootReducer 時,內(nèi)部會遍歷所有子 reducer把敢,然后根據(jù)每個子 state 是否發(fā)生改變寄摆,返回新舊的 根 state;

function combineReducer(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') {
          finalReducers[key] = reducers[key];
        }
      }

      var finalReducerKeys = Object.keys(finalReducers);

    return combine(state, action) {
        // 遍歷所有的 reducer修赞,根據(jù)前后 state 是否發(fā)生變化返回新舊 state

    var hasChanged = false;
    var nextState = {};

    for (var j = 0; j < finalReducerKeys.length; j++) {
      var key = finalReducerKeys[j];
      var reducer = finalReducers[key];
      var prevStateForKey = state[key];
      var nextStateForKey = reducer(prevStateForKey, action);
      nextState[key] = nextStateForKey;
      hasChanged = hasChanged || nextStateForKey !== prevStateForKey;
    }

    hasChanged = hasChanged || finalReducerKeys.length !== Object.keys(state).length;

    return hasChanged ? newState : state;
    }
}

createStore

createStore 主要是封裝 state婶恼、dispatch 和 subscribe 等方法的倉庫,提供 UI 組件數(shù)據(jù)和發(fā)射特定類型 action柏副;

function createStore(reducer, prelaodedState, enhancer) {
    var isDispatching = false;
  var currentReducer = reducer;
  var currentState = preloadedState;
  var currentListeners = [];
  var nextListeners = currentListeners;

  function getState() {
    return currentState;
  }

  function dispatch(action) {
    try {
      isDispatching = true;
      currentState = currentReducer(currentState, action);
    } finally {
      isDispatching = false;
    }

    var listeners = (currentListeners = nextListeners);

    for (var i = 0; i < listeners.length; i++) {
      var listener = listeners[i];
      listener();
    }

    return action;
  }

    var isSubscribed = true;
    ensureCanMutateNextListeners();
    nextListeners.push(listener);

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

      isSubscribed = false;
      ensureCanMutateNextListeners();
      var index = nextListeners.indexOf(listener);
      nextListeners.splice(index, 1);
      currentListeners = null;
    }
  }

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice();
    }
  }

    // 先調(diào)用 dispatch勾邦,初始化 state
  dispatch({ type: `@@redux/INIT${/* #__PURE__ */ randomString()}` });

    const store = ({
    dispatch,
    subscribe,
    getState,
  });
    return store;
}

Provider

react-redux 的 Provider 組件采用 React 的 Context 數(shù)據(jù)傳遞機制,通過 context 對象將 store 和 state 綁定到各個組件上割择;

這里在源碼的 Provider 組件在實現(xiàn)上進行一定的簡化眷篇,分離出核心代碼:

function Provider({ store, context, children }) {
  var Context = Context || React.createContext(null);
  var contextValue = useMemo(() => {
    return {
      store,
    };
  }, [store]);

  return <Context.Provider value={contextValue}>
    {children}
  </Context.Provider>
}

connect

react-redux 的 connect 組件是一個高階組件,內(nèi)部通過 useContext 去消費 Provider 提供的 context荔泳,將 context.store 和 context.store.getState() 以 props 的方式傳遞給 connect 的組件蕉饼,并監(jiān)聽 context.store 的變化;

function createConnect({
  connectHOC = connectAdvanced,
  selectorFactory,
}) {
  return function connect({
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
  }) {
    return connectHOC(selectorFactory, {
      mapStateToProps,
      mapDispatchToProps,
    });
  }
}

function connectAdvanced(selectorFactory, {
  context = React.createContext(null),
  ...connectOptions,
}) {
  // 這里 context 是從 parent Provider 給的
  var Context = context;

  return function wrapWithConnect(WrappedComponent) {

    function Connect(props) {
      var contextValue = useContext(Context);
      var store = props.store ? props.store : contextValue.store;
      // 實際的框架玛歌,通過 mapStateToProps 將根 state 的特定子 state 合并到 props
      var state = store.getState();
      return <WrappedComponent {...store, ...state} />
    }

    return Connect;
  }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昧港,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子支子,更是在濱河造成了極大的恐慌创肥,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件值朋,死亡現(xiàn)場離奇詭異叹侄,居然都是意外死亡,警方通過查閱死者的電腦和手機吞歼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門圈膏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篙骡,你說我怎么就攤上這事稽坤。” “怎么了糯俗?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵尿褪,是天一觀的道長。 經(jīng)常有香客問我得湘,道長杖玲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任淘正,我火速辦了婚禮摆马,結(jié)果婚禮上臼闻,老公的妹妹穿的比我還像新娘。我一直安慰自己囤采,他們只是感情好述呐,可當(dāng)我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蕉毯,像睡著了一般乓搬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上代虾,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天进肯,我揣著相機與錄音,去河邊找鬼棉磨。 笑死江掩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的含蓉。 我是一名探鬼主播频敛,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼馅扣!你這毒婦竟也來了斟赚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤差油,失蹤者是張志新(化名)和其女友劉穎拗军,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓄喇,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡发侵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妆偏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刃鳄。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钱骂,靈堂內(nèi)的尸體忽然破棺而出叔锐,到底是詐尸還是另有隱情,我是刑警寧澤见秽,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布愉烙,位于F島的核電站,受9級特大地震影響解取,放射性物質(zhì)發(fā)生泄漏步责。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔓肯。 院中可真熱鬧遂鹊,春花似錦、人聲如沸省核。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽气忠。三九已至,卻和暖如春赋咽,著一層夾襖步出監(jiān)牢的瞬間旧噪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工脓匿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留淘钟,地道東北人。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓陪毡,卻偏偏與公主長得像米母,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子毡琉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容