美文网首页
Redux 源码剖析

Redux 源码剖析

作者: 犯迷糊的小羊 | 来源:发表于2021-03-24 20:59 被阅读0次

    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;
      }
    }
    
    

    相关文章

      网友评论

          本文标题:Redux 源码剖析

          本文链接:https://www.haomeiwen.com/subject/kyfkhltx.html