React 学习系列(四): Redux 深入学习

1,119 阅读11分钟

引入

在使用 React 构建web应用的时候, 我们一般会通过 props 属性来进行父子间组件之间的通讯。但是当应用比较复杂,需要多个组件之间进行通讯时, 再使用 props 属性就不太合适了。此时, 我们可以使用 redux 来进行组件之间的通讯。

redux 关键对象及三大原则

redux 中, 有几个关键对象需要我们先简单了解一下:

  • State

    web应用的 全局数据, 供应用中的多个组件共同使用。

  • Store

    存储全局数据 State 的地方, 我们可以通过 store.getState() 方法获取全局对象 State

  • action

    本质上是一个普通的 js对象, 对象中包含我们要修改的数据。我们一般通过调用 store 对象的 dispatch() 方法,将修改后的数据传递到 store

  • reducer

    本质上是一个 纯函数,会返回一个 state 对象。当我们调用 store.dispatch(action) 时,就会执行 reducer函数reducer函数 会接收旧的 stateaction, 然后返回一个新的 state

redux 的关键对象及其用法, 构成了 Redux 使用的三大原则:

  • 单一数据源

    一个 React应用 应且仅有一个 Store对象Store对象 中包含着整个react应用的所有 全局数据State

  • state是只读的

    全局数据State 是只读的, 我们不能直接修改 State, 只能通过 store对象dispatch 方法来触发 action,来修改 State

  • 使用纯函数来执行修改

Store

Store 是整个应用保存全局数据(State)的地方,每个应用应且只有一个 Store。 通过 Store, 我们可以获取、修改 State

Redux 提供 createStore 方法, 来构建 Store 对象。

    import {createStore} from 'redux';
    // fn是一个函数,为 reducer, 会返回一个 state
    let store = createStore(fn)
    

Store 对象提供了一系列 API 供用户使用:

  • getState()

    返回当前应用的全局数据 State

  • dispatch(action)

    分发 action,改变应用的全局数据 statedispatch方法是触发 State 变化的唯一途径。

  • subscribe(listener)

    添加一个变化监听器 - listener, 每当 dispatch(action) 的时候就会执行。

  • replaceReducer(nextReducer)

    替换 store 当前用来计算 statereducer

action

由于 State 是只读的, 我们不能直接修改 State。通过 Action, 我们可以 表达想要修改 State 的意图, 通知 Redux 来修改 State

Action 本质上一个 js对象 , 格式如下所示。其中, type 属性是必须的,是 Action 的名称,表示一类修改 State 的方式, 其他属性可以自由设置。

    const action = {
        type: 'ADD_TODO', // 将要执行的动作: ADD_TODO, 向任务列表中添加内容
        text: '123', // 添加的内容的名称
        ...
    }

在应用中, 每次修改 State, 我们都需要定义对应的 Action对象, 相同类型 Action对象, 我们可以通过 Action Creator函数 来定义。

    const addTodo = text => {
        type: 'ADD_TODO',
        text
    }  // 我们通过执行addTodo('123'), 返回一个Action对象{type: 'ADD_TODO', text: '123'}

定义好 Action对象 之后, 我们必须通过 dispatch 方法, 发送 Action对象, 然后触发 State对象 的修改。

    const store = Store.createStore(fn);
    store.dispatch(addTodo('123'));

reducer

Action对象 仅仅表达了想要修改 State 的意图, 我们还需要定义一个对象来表达如何修改State对象, 这个对象就是 Reudecer 对象

const todos = (state = [], action) {
    switch(action.type) {
        case 'ADD_TODO':
            return [
                ...state, {
                    text: action.text
                }
            ];
        default:
            return state;
    }
}

Reducer, 本质上是一个 函数, 它接受 当前state对象Action对象 作为参数,返回 新的state对象

Redux API 解析

  • createStore(reducer, [prelodedState], enhancer)

    • 功能

      创建一个 Store对象 来存放应用中的所有 全局state一个应用中有且应有一个Store对象

    • 参数
      • reducer | function

        接收两个参数, 分别是 当前管理的State数据Action对象, 返回一个新的 State 数据

      • preloadedState | any

        初始化时的 State数据

      • enhancer | function

        函数增强器,是一个函数, 返回一个新的 store create

        执行新的 store create 方法时, 会生成一个 store 对象, store 对象的 dispatch方法 会被函数增强器包装。

    • 返回值

      返回一个 store 对象, 保存应用中的所有 state 数据。 一般在使用第三方中间件时, 会添加 enhancer参数 来构建 store对象

    • 用法示例
          import { createStore, applyMiddleware } from 'redux';
          import thunk from 'redux-thunk'
          import { createLogger } from 'redux-logger'
          import reducers from './reducers/index'
          
          // redux 中间件
          const middleware = [thunk, createLogger];
          
          // 构建一个store对象
          const store = createStore(reducers);
          
          // 利用reducers, store增强器, 构建一个store对象
          const store = createStore(reducers, applyMiddleware(middleware));
      
    • 源码解析
          /**
           * 构建store对象
           * @param reducer  用户提供的 reducer,用于返回更新以后的 state, 是一个纯函数
           * @param preloadedState 构建 store 对象时传入的初始 state
           * @param enhancer 函数增强器, 应用中间件
           * @return Store 返回一个构建的 store 兑现
           **/
          export default function createStore(reducer, preloadedState, enhancer) {
            // 对应 createStore(reducer, enhancer) 这种情况
            if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
              enhancer = preloadedState
              preloadedState = undefined
            }
          
            if (typeof enhancer !== 'undefined') {
              if (typeof enhancer !== 'function') {
                throw new Error('Expected the enhancer to be a function.')
              }
              
              // 如果传入了 enhancer, 则将旧的 store creater 传入 enhance,
              // 返回一个新的 store creater, 并传入 reducer、preloadedState执行
              return enhancer(createStore)(reducer, preloadedState)
            }
           }
           
           ...
          // store 对象当前使用的 reducer
          var currentReducer = reducer;
          // store 对象对应的 state
          var currentState = preloadedState;
          // store 对象通过 subscribe 注册的 listeners
          var currentListeners = [];
          // 是否处于 dispatch 过程中
          var isDispatching = false;
           
           ...
           // 获取当前应用的全局 state数据
           function getState() {
              // reducer、dispatch 过程中不能使用 getState
              if (isDispatching) {
                throw new Error('You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.');
              }
              // 返回当前的 state
              return currentState;
           }
           // 注册一个 listener, 监听 state 数据的变化
           // state 更新时触发
           function subscribe(listener) {
              if (typeof listener !== 'function') {
                  throw new Error('Expected the listener to be a function.');
              }
              // reducer 执行期间不能注册 listener?
              if (isDispatching) {
                   throw new Error('You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.');
              }
              
              var isSubscribed = true;
              ensureCanMutateNextListeners();
              nextListeners.push(listener);
              // 返回一个函数, 用于卸载注册的 listener
              return function unsubscribe() {
                  if (!isSubscribed) {
                      return;
                  }
              
                  if (isDispatching) {
                      throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.');
                  }
              
                  isSubscribed = false;
                  ensureCanMutateNextListeners();
                  var index = nextListeners.indexOf(listener);
                  // 卸载注册的 listener
                  nextListeners.splice(index, 1);
              };
           }
           // 分发action, 修改state
          function dispatch(action) {
              // action 必须是一个对象
              if (!isPlainObject(action)) {
                throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
              }
              // action 对象必须有 type 属性
              if (typeof action.type === 'undefined') {
                throw new Error('Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?');
              }
              // dispatch 过程中不能再次 dispatch
              if (isDispatching) {
                throw new Error('Reducers may not dispatch actions.');
              }
          
              try {
                isDispatching = true;
                // 使用 reducer 计算新的 state
                // reducer 应该是一个纯函数, 不要试图直接修改 state、action, 而是用返回值替换 state
                currentState = currentReducer(currentState, action);
              } finally {
                isDispatching = false;
              }
              // 收集的 listener
              var listeners = currentListeners = nextListeners;
              // 更新 state 以后, 触发注册的 listener
              for (var i = 0; i < listeners.length; i++) {
                var listener = listeners[i];
                listener();
              }
              // 返回传入的 action 对象
              return action;
          }
          // 替换 store 当前用来计算 state 的 reducer
          function replaceReducer(nextReducer) {
              // nextReducer 必须是一个函数
              if (typeof nextReducer !== 'function') {
                throw new Error('Expected the nextReducer to be a function.');
              }
              // 替换 reducer
              currentReducer = nextReducer; 
              // 替换 reducer 以后, 更新 state
              dispatch({
                type: ActionTypes.REPLACE
              });
          }
           
           ...
           
          // 执行 reducer,初始化 state
          dispatch({
              type: ActionTypes.INIT
          });
           
          return {
              dispatch,
              subscribe,
              getState,
              replaceReducer,
              [$$observable]: observable
            }
      
  • applyMiddleware(...middleware)

    • 功能

      扩展 Redux, 使用 middleware包装 store 对象的 dispatch 方法

    • 参数

      ...middleware | arguments

      每个 middleware 接受 StoredispatchgetState 函数作为命名参数,并返回一个函数。

      ({getState, dispatch}) => next => action

      next => action, next 为上一个 middleware 返回的新的 dispatch 方法,action 为返回的一个新的接收 actiondispatch方法

      中间件middleware调用的顺序为从右到左

    • 返回值

      返回一个应用了 middleware 的 store enhancer

      enhancer是一个函数,接收 store creater, 返回一个 新的 store creater

      即:createStore => createStore

    • 用法实例
          import { createStore, applyMiddleware } from 'redux';
          import thunk from 'redux-thunk'
          import { createLogger } from 'redux-logger'
          import reducers from './reducers/index'
          
          // redux 中间件
          const middleware = [thunk, createLogger];
          
          // 利用reducers, store增强器, 构建一个store对象
          const store = createStore(reducers, applyMiddleware(middleware));
      
    • 源码解析
          // applyMiddleware接收一系列middleware, 然后返回一个enhancer
          export default function applyMiddleware(...middlewares) {
          
            // 返回enhancer, createStore => createStore
            return (createStore) => (reducer, preloadedState, enhancer) => {
            
              // 执行 redux 原生的 createStore方法, 此时 enhancer 为 undefined
              const store = createStore(reducer, preloadedState, enhancer)
              // redux 原生的 dispatch 方法
              let dispatch = store.dispatch
              // 中间件链
              let chain = []
              // redux 原生的 getState 和 dispatch
              const middlewareAPI = {
                getState: store.getState,
                dispatch: (action) => dispatch(action)
              }
              
              // [chainA, chainB, chainC, chainD], 格式为 next => action
              // 每个 chain 中都通过闭包保存 redux 原生的 store、 store.getState、store.dispatch
              chain = middlewares.map(middleware => middleware(middlewareAPI))
              
              // 即 chainA(chainB(chainC(chainD(store.dispatch))))
              // 使用中间件包装原生的 dispatch 方法
              dispatch = compose(...chain)(store.dispatch)
              
              // 返回一个新的Store对象, dispatch方法被重新包装
              return {
                ...store,
                dispatch
              }
            }
          }
      
  • compose(...func)

    • 功能

      从右到左组合多个函数, 返回一个最终的函数。

      即将 [funcA, funcB, funcC, funcD], 合成 funcA(funcB(func(funcD(params))))

    • 参数

      需要合成的多个函数。预计每个函数都接收一个参数。它的返回值将作为一个参数提供给它左边的函数,以此类推。例外是 最右边的参数可以接受多个参数

    • 返回

      从右到左把接收到的函数合成的最终函数。

    • 用法实例
          import { createStore, applyMiddleware, compose } from 'redux'
          import thunk from 'redux-thunk'
          import { createLogger } from 'redux-logger'
          import reducer from '../reducers/index'
          
          const store = createStore(
            reducer,
            // thunk(createLogger(createStore)), 返回一个新的createStore
            compose(
              applyMiddleware(thunk),
              applyMiddleware(createLogger)
            )
          )
      
    • 源码解析
          export default function compose(...funcs) {
           if (funcs.length === 0) {
             return arg => arg
           }
         
           if (funcs.length === 1) {
             return funcs[0]
           }
         
           return funcs.reduce((a, b) => (...args) => a(b(...args)))
         }
      
  • combineReducers(reducers)

    • 功能

      将多个 reducer 合并为一个 combine reducer。调用 dispatch 派发 action 时,使用 combine reducer 来更新 state

      combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。

    • 参数
      • reducers | object

        参数 reducers 是一个普通对象,属性值为需要合并的 reducer

    • 返回值

      一个调用 reducers 对象里所有 reducercombine reducer

      使用 combine reducer 时,会将 reducers 中包含的所有 reducer 都触发一遍。

      执行 combine reducer 会构造一个与 reducers 对象结构相同的 state 对象。

    • 用法示例

      具体用法详见: combineReducers

    • 源码解析
      /**
       * 将多个 reducer 合并生成一个 combine reducer 
       * 在执行 createStore 的时候, 会触发 combine reducer, 初始化 state
       * state 是一个对象, key 是 reducers 的 key, 对应的值为执行 reducer 以后的结果
       * 使用 合并reducer 以后,只要执行 dispatch, 每一个 reducer 都要触发一次。
       * @param {*} reducers 一个包含多个 reducer 的对象
       */
      function combineReducers(reducers) {
        // 所有 reducers 的 key
        var reducerKeys = Object.keys(reducers);
        // 合并以后的最终 reducer
        var finalReducers = {};
        // 遍历 reducers, 收集满足条件的 reducer
        for (var i = 0; i < reducerKeys.length; i++) {
          var key = reducerKeys[i];
          ...
          if (typeof reducers[key] === 'function') {
            // 要合并的 reducer 必须是一个函数
            finalReducers[key] = reducers[key];
          }
        }
        // 最终的 reducer 的 key
        var finalReducerKeys = Object.keys(finalReducers); 
      
        var unexpectedKeyCache;
      
        {
          unexpectedKeyCache = {};
        }
      
        var shapeAssertionError;
      
        try {
          // 校验每一个 reducer 的返回值是否是非 undefined
          assertReducerShape(finalReducers);
        } catch (e) {
          shapeAssertionError = e;
        }
        // 返回一个最后合并生成的的 combine reducer, 供 dispatch 触发
        return function combination(state, action) {
          // 如果 state 是 undefined, 那么将 state 初始化为一个空对象 : {}
          // 调用 createStore 初始化 state 会出现这种情况
          if (state === void 0) {
            state = {};
          }
          
          ...
      
          var hasChanged = false;
          // 新的 state
          var nextState = {};
          // 遍历要合并的所有 reducer
          for (var _i = 0; _i < finalReducerKeys.length; _i++) {
            // reducer 的 key
            var _key = finalReducerKeys[_i];
            // 相应的 reducer
            var reducer = finalReducers[_key];
            // 获取 reducer 对应的 state
            var previousStateForKey = state[_key];
            // 更新以后的 state
            var nextStateForKey = reducer(previousStateForKey, action);
      
            if (typeof nextStateForKey === 'undefined') {
              var errorMessage = getUndefinedStateErrorMessage(_key, action);
              throw new Error(errorMessage);
            }
            // 构建 reducer 对应的 state
            nextState[_key] = nextStateForKey;
            // 判断 state 是否发生更新
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
          }
      
          return hasChanged ? nextState : state;
        };
      }
      
  • bindActionCreators

    • 功能

      actionCreatordispatch 合并,返回一个函数。执行这个函数时,先构建 action 对象, 然后通过 dispatch 方法派发 action, 触发 state 更新。

    • 参数
      • actionCreators | object | function

        参数 actionCreators 可以是一个 function, 用于构建一个 action。 也可以是一个普通对象, 对象中的每一个属性值都是一个 actionCreator。

      • dispatch | function

        派发 action, 触发 state 更新。

    • 返回值

      bindActionCreators 方法的返回值是 一个函数 或者 对象

      如果传入的 actionCreators 是一个函数, 那么返回值也是一个函数,这个函数合并 actionCreator 和 dispatch,执行时会先通过 actionCreator 构建 action 对象,然后通过 dispatch 派发 action 触发 state 更新。

      如果传入的 actionCreators 是一个对象, 那么返回值也是一个对象。对象的每一个属性值都是合并 actionCreator 和 dispatch 以后生成的方法。

    • 用法示例

      具体用法详见官网 bindActionCreators

    • 源码解析
      /**
       * 将 dispatch 方法和 actionCreator 方法合并一个方法
       * 执行这个方法时先创建 action, 再调用 diapatch 方法
       * @param {*} actionCreator  actionCreator
       * @param {*} dispatch 派发 action, 触发 state 的更新
       */
      function bindActionCreator(actionCreator, dispatch) {
        return function () {
          return dispatch(actionCreator.apply(this, arguments));
        };
      }
      
      /**
       * 将多个 actionCreators 和 dispatch合并
       * @param {*} actionCreators 一个普通对象, 包含多个 actionCreators
       * @param {*} dispatch 派发 action, 触发 state 的更新
       */
      function bindActionCreators(actionCreators, dispatch) {
        // 如果 actionCreators 是一个函数, 直接调用 bindActionCreator 返回合并以后的方法
        if (typeof actionCreators === 'function') {
          return bindActionCreator(actionCreators, dispatch);
        }
        // 如果 actionCreators 不是对象或者为空, 抛出异常
        if (typeof actionCreators !== 'object' || actionCreators === null) {
          throw new Error("bindActionCreators expected an object or a function, instead received " + (actionCreators === null ? 'null' : typeof actionCreators) + ". " + "Did you write \"import ActionCreators from\" instead of \"import * as ActionCreators from\"?");
        }
        // 合并以后的方法集合
        var boundActionCreators = {};
        // 遍历 actionCreators 中的 actionCreator,通过 bindActionCreator 生成合并方法
        for (var key in actionCreators) {
          var actionCreator = actionCreators[key];
      
          if (typeof actionCreator === 'function') {
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
          }
        }
        // 返回合并以后的方法集合
        return boundActionCreators;
      }