Redux 原理以及实现

141 阅读3分钟

原理概述

  • 定义一个Store
  • store.state用于存储状态数据
  • store.dispatch(action)来更改state状态
  • reducer(state,action)函数来定义和关联actionstate变更操作的对应关系
  • store.subscribe(callback)来收集state更新后回调函数

实现

Store Class

class Store {
  constructor(initialState, reducers) {
    this.state = initialState || {};
    this.reducer = this.combineReducers(reducers);
    this.subs = [];
  }

  getState = () => this.state;
  setState = state => this.state = state;
  subscribe = callback => this.subs.push(callback.bind({
    state: this.getState()
  }));

  dispatch = action => {
    this.setState(this.reducer(this.getState(), action));
    this.subs.forEach(sub => sub());
  }
  combineReducers = (reducers) => {
    return (state = this.getState(), action) => {
      // 遍历执行 reducers 函数, 只会变更 state 对象中的内容, 保持其引用不变
      Object.keys(reducers).forEach(fnKeyFromReducer => {
        let newState = reducers[fnKeyFromReducer](state, action);
        // 合并新旧 state, 最后在 dispatch 中一次性 setState
        // 将变更合并到原本的 initialState , 注意是同一个对象的引用地址
        Object.assign(state, newState);
      });
      // 注意此处返回的 state 是依然是 initialState 的引用
      return state;
    }
  }
}

createStore

function createStore(initialState = {}, reducers) {
  let store = new Store(initialState, reducers);
  return store;
}

demo

<body>
  <div id="app"></div>
  <button onclick="add()">add</button>
  <button onclick="reduce()">reduce</button>
  <script>
    let obj = {
      count: 0
    };
    
    let reducers = {
      add: function (state, action) {
        if (action.type === 'add') {
          return Object.assign({}, state, {
            count: state.count + 1
          })
        }
        return state;
      },
      reduce: function (state, action) {
        if (action.type === 'reduce') {
          return Object.assign({}, state, {
            count: state.count - 1
          })
        }
        return state;
      }
    };
    
    let store = createStore(obj, reducers);

    store.subscribe(function () {
      console.log(obj === this.state); // true , state 只是变更内容, 其原始initialState引用不变
      document.querySelector('#app').textContent = this.state.count;
    });

    function add() {
      store.dispatch({
        type: 'add',
      })
    }
    function reduce() {
      store.dispatch({
        type: 'reduce',
      })
    }

  </script>
</body>

实现中间件 applyMiddleware

Store

class Store {
  constructor(initialState, reducers) {
    this.state = initialState;
    this.reducer = this.combineReducers(reducers);
    this.subs = [];
    this.initialDispatch(); // 初始化默认的 dispatch
  }

  getState = () => this.state;
  setState = state => this.state = state;
  // 此处 bind 只是为了遵循迪米特原则, 即最少知道原则, 可选
  subscribe = callback => this.subs.push(callback.bind({
    state: this.getState()
  }));

  initialDispatch = (dispatch) => {
    if (dispatch) { // 若外部传入集成了中间件函数的 dispatch , 则覆盖默认 dispatch
      return this.dispatch = dispatch;
    }
    this.dispatch = action => {
      // 执行 reducers 函数, 只变更 state 对象中的内容, 其引用不变
      this.setState(this.reducer(this.getState(), action));
      this.subs.forEach(sub => sub());
    }
  }

  combineReducers = (reducers) => {
    return (state = this.getState(), action) => {
      Object.keys(reducers).forEach(fnKeyFromReducer => {
        let newState = reducers[fnKeyFromReducer](state, action);
        // 合并新旧 state, 最后在 dispatch 中一次性 setState
        // 即将变更合并到原本的 initialState , 注意是同一个对象的引用地址
        Object.assign(state, newState);
      });
      // 注意此处返回的 state 是依然是 initialState 的引用
      return state;
    }
  }
}

createStore && applyMiddleware

// 第三个参数是增强版的 dispatch
function createStore(initialState = {}, reducers, enhancedDispatch) {
  let store = new Store(initialState, reducers);
  // 覆盖默认 dispatch
  store.initialDispatch(enhancedDispatch(store));
  return store;
}

// 接收一个数组
// 要求该数组元素为函数且形如 : (store) => (next) => (action) => {}
// store: 为了让中间件函数能访问到 store 对象
// next: 被内置嵌套的下一个中间件函数
// action: 调用 dispatch(action) 时需要能访问 action
let applyMiddleware = (...middlewareArray) => (store) => {
  // 逆序嵌套所有的中间件函数, 生成集成所有中间件函数增强版 dispatch
  if (middlewareArray.length) {
    let innerMethod;
    // fn 要求: 一个用于接收 store.dispatch 的形参; 返回值是一个函数
    // 由于我们要将原始的 store.dispatch 作为 fn 函数体的一部分嵌入, 所以要求 fn 中要有一个形参用于接收 store.dispatch 并在其函数体内的相应的位置调用即可
    // 由于 fn 可能还要被内置于其前面的函数, 所以返回值必须为一个函数
    middlewareArray.forEach(fn => {
      // 为了能访问到 store 对象, 故需要再嵌套一层函数来接收 store
      //最小访问策略, 只暴露 getState 给中间件函数
      const smStore = {
        getState: store.getState
      }
      if (!innerMethod) innerMethod = fn(smStore)(store.dispatch);
      else innerMethod = fn(smStore)(innerMethod);
    });
    // innerMethod 就是最终版的 dispatch , 其调用方式依然是 dispatch(action)
    return innerMethod;
  }
}

redux 实现 applyMiddleware 的方式

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */
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)))
}
// applyMiddleware
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,
    }
  }
}

demo

<body>
  <div id="app"></div>
  <button onclick="add()">add</button>
  <button onclick="reduce()">reduce</button>
  <script>

    let obj = {
      count: 0
    };
    let reducers = {
      add: function (state, action) {
        if (action.type === 'add') {
          return Object.assign({}, state, {
            count: state.count + 1
          })
        }
        return state;
      },
      reduce: function (state, action) {
        if (action.type === 'reduce') {
          return Object.assign({}, state, {
            count: state.count - 1
          })
        }
        return state;
      }
    };
    
    // 中间件函数
    let logMiddleware = (store) => (next) => (action) => {
      console.log('log middle ware --- start');
      next(action);
      console.log('log middle ware --- end');
    }
    const loggerMiddleware = (store) => (next) => (action) => {
      console.log('---before state::', store.getState());
      console.log('---doing dispatch type::', action.type);
      next(action);
      console.log('---after state::', store.getState());
      console.log('---------divide--------');
    }

    // 集成中间件函数
    let enhancedDispatch = applyMiddleware(loggerMiddleware, logMiddleware);

    let store = createStore(obj, reducers, enhancedDispatch);

    store.subscribe(function () {
      console.log(obj === this.state); // true , state 只是变更内容, 其原始initialState引用不变
      document.querySelector('#app').textContent = this.state.count;
    });

    function add() {
      store.dispatch({
        type: 'add',
      })
    }
    function reduce() {
      store.dispatch({
        type: 'reduce',
      })
    }

  </script>
</body>