Redux 源碼剖析

Redux

Redux 是為了處理應(yīng)用復雜狀態(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,當調(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容