Redux 源码剖析

Redux

Redux 是为了处理应用复杂状态流而设计的状态管理库,它吸收了 Flux 架构和函数式编程的优秀思想,提出了应用分层设计的解决方法;

Redux 基本架构

Redux 的将应用分为 Actions、State 和 View 三层;

Actions

Actions 描述用户操作的基本信息,包括操作类型和所需传递的数据;

在代码层面看,一个 action 就是一个对象,实际编码过程中会将 action 设计为 action creator,里面直接封装 action.type,只需要传递数据。

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

Reducers

Reducer 是根据 action 类型生成新的 state 的函数。这里要求 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)]

数据流向

这里以 React 这一 UI 框架为例,讲解一下 React + Redux 的基本数据流向;

  • 首先,UI 从 Store 里面获取 State,这里通过 react-redux 的 Provider 组件实现 store 的注入;
  • UI 发生交互后,会调用 dispatch(action(payload)) 方法,dispatch 方法默认挂载在 store 上;
  • dispatch 触发后,会调用 rootReducer(),rootReducer 会根据之前的 state 和 action 计算新的 state;
  • 新的 state 会重新从根组件传递下去,如果 state 发生变化,则 re-rerender 对应的组件,从而实现视图的更新;

源码解析

源码以 redux 和 react-redux 为内容,为了避免干扰,将会在源码基础上去除本身边界条件、状态锁以及干扰分析部分的代码,并进行简化;

combineReducer

combineReducer 是一个高阶函数, 作用就是将所有的子 reducer 合并为一个根 reducer,当调用 rootReducer 时,内部会遍历所有子 reducer,然后根据每个子 state 是否发生改变,返回新旧的 根 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,根据前后 state 是否发生变化返回新旧 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 组件数据和发射特定类型 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();
    }
  }

    // 先调用 dispatch,初始化 state
  dispatch({ type: `@@redux/INIT${/* #__PURE__ */ randomString()}` });

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

Provider

react-redux 的 Provider 组件采用 React 的 Context 数据传递机制,通过 context 对象将 store 和 state 绑定到各个组件上;

这里在源码的 Provider 组件在实现上进行一定的简化,分离出核心代码:

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 组件是一个高阶组件,内部通过 useContext 去消费 Provider 提供的 context,将 context.store 和 context.store.getState() 以 props 的方式传递给 connect 的组件,并监听 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;
  }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容