• redux原理分享


    概述

    • 一个状态管理工具
    • Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个 Store。
    • State:包含所有数据,如果想得到某个时点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State。
    • Action:Action 就是 View 发出的通知,表示 State 应该要发生变化了。
    • Action Creator:View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦,所以我们定义一个函数来生成 Action,这个函数就叫 Action Creator。
    • Reducer:Store 收到 Action 以后,必须给出一个新的 State。这种 State 的计算过程就叫做 Reducer。Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。
    • dispatch:是 View 发出 Action 的唯一方法。

    整个工作流程:

    1. 首先,用户(通过 View)发出 Action,发出方式就用到了 dispatch 方法。
    2. 然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action,Reducer 会返回新的 State
    3. State 一旦有变化,Store 就会调用监听函数,来更新 View。

    三大原则

    1.单一数据源(Store) 整个应用的State被存放在一棵Object tree中,并且这个Object tree只存在唯一一个Store中;

    2.State是只读的 唯一改变 State 的方法就是触发 Action,Action 是一个用于描述已发生事件的普通对象。 确保了所有的修改都能被集中化处理。

    3.通过纯函数Reducer来修改Store, Reducer 只是一些纯函数,它接收先前的 State 和 Action,并返回新的 State。 即reducer(state, action) => new state

    createStore创建store

    • createStore 方法接受 3 个参数参数 (reducer, [preloadedState], enhancer);
      返回 store,store 上挂载着 dispatch、getState、subscribe、replaceReducer 等方法
    • 第二个参数是 preloadedState,它是 state 的初始值,实际上他并不仅仅是扮演着一个 initialState 的角色,如果我们第二个参数是函数类型,createStore 会认为传入了一个 enhancer,如果我们传入了一个 enhancer,createStore 会返回 enhancer(createStore)(reducer, preloadedState)的调用结果,这是常见高阶函数的调用方式。
    • enhancer 接受 createStore 作为参数,对 createStore 的能力进行增强,并返回增强后的 createStore。然后再将 reducer 和 preloadedState 作为参数传给增强后的 createStore,最终得到生成的 store。
    export default function createStore(reducer, preloadedState, enhancer) {
      if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        // 第二个参数是一个函数,没有第三个参数的情况
        enhancer = preloadedState;
        preloadedState = undefined;
      }
      // 如果第三个参数是函数走下面的逻辑,返回一个新的createStore
      if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          // enhancer 不是函数就报错
          throw new Error('Expected the enhancer to be a function.');
        }
        // enhancer就是高阶函数,强化了本身这个createStore的函数,拿到增强后的createStore函数去处理
        // applyMiddleware这个函数还会涉及到这个
        return enhancer(createStore)(reducer, preloadedState);
      }
      if (typeof reducer !== 'function') {
        // reducer不是函数报错
        throw new Error('Expected the reducer to be a function.');
      }
      // 其他代码省略
      return {
        dispatch,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable,
      };
    }
    

    applyMiddleware 应用中间件

    • 返回一个函数 enhancer;
    • 右边中间件的执行时机由左边的中间件决定,这是因为 next 的方法的调用时机由右边控制
    • dispatch 的处理使用了闭包,这样保证在中间件中调用 dispatch 也是用的最终的 dispatch,也是同一个 dispatch;
    • 写中间件的时候如果要调用 dispatch,一定要有跳出条件,防止死循环
    export default function applyMiddleware(...middlewares) {
      return (createStore) =>
        (...args) => {
          const store = createStore(...args);
          let dispatch = () => {
            throw new Error(
              'Dispatching while constructing your middleware is not allowed. ' +
                'Other middleware would not be applied to this dispatch.',
            );
          };
    
          const middlewareAPI = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args),
          };
          const chain = middlewares.map((middleware) => middleware(middlewareAPI));
    
          dispatch = compose(...chain)(store.dispatch);
          return {
            ...store,
            dispatch,
          };
        };
    }
    // 其实就是修改了dispatch
    
    let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);
    

    combineReducers 合并多个reducer

    从执行结果看,这时候 state 已经变成了一个以这些 reducer 为 key 的对象;reducer 也变成了一个合并的 reducer;
    遍历执行所有的 reducer 的时候把 action 传进去,返回新的 state;

    export default function combineReducers(reducers) {
      const reducerKeys = Object.keys(reducers);
      const finalReducers = {};
      for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i];
    
        if (typeof reducers[key] === 'function') {
          finalReducers[key] = reducers[key];
        }
      }
      const finalReducerKeys = Object.keys(finalReducers);
      /* 返回一个整合后的reducers */
      return function combination(state = {}, action) {
        let hasChanged = false;
        const nextState = {};
        for (let i = 0; i < finalReducerKeys.length; i++) {
          const key = finalReducerKeys[i];
          const reducer = finalReducers[key];
          const previousStateForKey = state[key];
          const nextStateForKey = reducer(previousStateForKey, action);
          if (typeof nextStateForKey === 'undefined') {
            const errorMessage = getUndefinedStateErrorMessage(key, action);
            throw new Error(errorMessage);
          }
          nextState[key] = nextStateForKey;
          hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
        }
        return hasChanged ? nextState : state;
      };
    }
    

    dispatch

    • 默认的 action 只能是普通对象,除非使用了第三方中间件,比如 redux-thunk
    function dispatch(action) {
      if (!isPlainObject(action)) {
        throw new Error(
          'Actions must be plain objects. ' +
            'Use custom middleware for async actions.',
        );
      }
    
      if (typeof action.type === 'undefined') {
        throw new Error(
          'Actions may not have an undefined "type" property. ' +
            'Have you misspelled a constant?',
        );
      }
    
      if (isDispatching) {
        throw new Error('Reducers may not dispatch actions.');
      }
      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;
    }
    

    中间件

    • Redux 中间件在发起一个 action 至 action 到达 reducer 的之间提供了一个第三方的扩展。本质上通过插件的形式,将原本的 action->redux 的流程改变为 action->middleware1->middleware2-> ... ->reducer,通过改变数据流,从而实现例如异步 Action、日志输入的功能。
    • Redux 中间件范式
      • 一个中间件接收 store 作为参数,会返回一个函数
      • 返回的这个函数接收老的 dispatch 函数作为参数(一般用 next 作为形参),会返回一个新的函数
      • 返回的新函数就是新的 dispatch 函数,这个函数里面可以拿到外面两层传进来的 store 和老 dispatch 函数
    function logger(store) {
      return function (next) {
        return function (action) { // 新的 dispatch 函数
          console.group(action.type);
          console.info('dispatching', action);
          let result = next(action);
          console.log('next state', store.getState());
          console.groupEnd();
          return result;
        };
      };
    }
    

    中间件的调用顺序

    • 首先调用顺序和书写顺序是一致的,但是这里面的洋葱模型包含了两次顺序,从洋葱出来的顺序是和书写顺序相反
    import React from 'react';
    import { createStore, applyMiddleware } from 'redux';
    function createLogger({ getState, dispatch }) {
      return (next) => (action) => {
        const prevState = getState();
        console.log('createLogger1');
        const returnValue = next(action);
        const nextState = getState();
        const actionType = String(action.type);
        const message = `action ${actionType}`;
    
        console.log(`%c prev state`, `color: #9E9E9E`, prevState);
        console.log(`%c action`, `color: #03A9F4`, action);
        console.log(`%c next state`, `color: #4CAF50`, nextState);
        return returnValue;
      };
    }
    
    function createLogger2({ getState }) {
      return (next) => (action) => {
        const console = window.console;
        const prevState = getState();
        console.log('createLogger2');
        const returnValue = next(action);
        const nextState = getState();
        const actionType = String(action.type);
        const message = `action ${actionType}`;
    
        console.log(`%c prev state2`, `color: #9E9E9E`, prevState);
        console.log(`%c action2`, `color: #03A9F4`, action);
        console.log(`%c next state2`, `color: #4CAF50`, nextState);
        return returnValue;
      };
    }
    const reducer = function (state = { number: 0 }, action) {
      switch (action.type) {
        case 'add':
          return {
            number: state.number + action.number,
          };
        default:
          return state;
      }
    };
    
    const store = createStore(
      reducer,
      applyMiddleware(createLogger, createLogger2),
    );
    store.subscribe(function () {
      console.log(1111);
    });
    const { dispatch } = store;
    const App = () => {
      const handler = () => {
        dispatch({ type: 'add', number: 10 });
      };
      return (
        <div>
          <button onClick={handler}>触发reduxbutton>
        div>
      );
    };
    export default App;
    

    store

    store 的属性如下:

    1. dispatch: ƒ dispatch(action)
    2. getState: ƒ getState()
    3. replaceReducer: ƒ replaceReducer(nextReducer)
    4. subscribe: ƒ subscribe(listener)

    redux 数据流

    Redux 的数据流是这样的:
    界面 => action => reducer => store => react => virtual dom => 界面

    bindActionCreators

    将action对象转为一个带dispatch的方法

    比如connect接收的mapDispatchToProps 是对象,会使用 bindActionCreators 处理; 接收 actionCreator 和 dispatch,返回一个函数;

    function bindActionCreator(actionCreator, dispatch) {
      // 返回一个函数
      return function() {
        return dispatch(actionCreator.apply(this, arguments))
      }
    }
    function bindActionCreators(actionCreators, dispatch) {
      if (typeof actionCreators === 'function') {
        return bindActionCreator(actionCreators, dispatch)
      }
      const boundActionCreators = {}
      for (const key in actionCreators) {
        const actionCreator = actionCreators[key]
        if (typeof actionCreator === 'function') {
          boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
        }
      }
      return boundActionCreators
    }
    
    const mapDispatchToProps = {  // actionCreators 这是个集合,
      onClick: (filter) => {
        type: 'SET_VISIBILITY_FILTER',
          filter: filter
      };
    }
    
    转换为:
    const mapDispatchToProps = {  // actionCreators 这是个集合,
      onClick:function(filter) {
        return dispatch({  // dispatch 是闭包中的方法
          type: 'SET_VISIBILITY_FILTER',
          filter: filter
        })
      }
    }
    

    compose

    函数套函数,compose(...chain)(store.dispatch)结果返回一个加强了的 dispatch;

    这点和koa比较相似,这个 dispatch 在执行的时候会调用中间件。

    function compose(...funcs) {
      if (funcs.length === 0) {
        return (arg) => arg;
      }
    
      if (funcs.length === 1) {
        return funcs[0];
      }
      // 每一次reduce迭代都会返回一个加强版的dispatch
      return funcs.reduce(
        (a, b) =>
          (...args) =>
            a(b(...args)),
      );
    }
    

    加强版 dispatch(一个方法,接收 action 参数),在中间件中用 next 表示,执行 next 之后,会形成一个链条。

    enhancer

    • enhancer,翻译过来就是 store 加强器,比如 applymiddleware 的返回值就是一个 enhaner。store 加强器可以重新构建一个更强大的 store,来替换之前的基础版的 store,让你的程序可以增加很多别的功能,比如 appllymiddleware 可以给你的 redux 增加中间件,使之可以拥有异步功能,日志功能等!
    // 以createStore为参数
    (createStore) =>
      (...args) => {};
    

    使用 redux 常遇见的问题

    • 样板代码过多 增加一个 action 往往需要同时定义相应的 actionType 然后再写相关的 reducer。例如当添加一个异步加载事件时,需要同时定义加载中、加载失败以及加载完成三个 actionType,需要一个相对应的 reducer 通过 switch 分支来处理对应的 actionType,冗余代码过多;

      目前已经存在着非常多的解决方案,比如dva redux-tookit等。

    • 更新效率问题:由于使用不可变数据模式,每次更新 state 都需要拷贝一份完整的 state 造成了内存的浪费以及性能的损耗。

      其实 redux 以及 react-redux 中内部已做优化,开发的时候使用 shouldComponentUpdate 等优化方法也可以应用,也可以用不可变数据结构如 immutable、Immr 等来解决拷贝开销问题。

  • 相关阅读:
    企业架构LNMP学习笔记22
    微信点赞测试点
    汉字风格迁移篇---W-net:基于深度神经网络的一次任意风格汉字生成
    商贸进销存软件怎么选?
    请勿使用PostMessage来模拟键盘输入
    流媒体分析之rtmp ffmpeg 实现-两种技术实现
    三、python基础——六大基本数据类型
    Synchronized同步锁
    【车载开发系列】UDS诊断---诊断故障清除($0x14)
    02 java ---- Android 基础app开发
  • 原文地址:https://www.cnblogs.com/walkermag/p/16930528.html