redux的使用以及实现原理

525 阅读3分钟

简介

Redux 是 JavaScript 状态容器,提供可预测化的状态管理,不依赖于任何第三方插件,例如vue也可以使用redux进行状态管理

redux常用的api有5种:

  • createStore:创建store容器
  • applyMiddleware:封装redux中间件函数
  • bindActionCreators:一般在react-redux中使用,对多个action函数进行封装,返回带有dispatch的自动调用的对象 例如({ action1, action2 }) => return { action1: dispatch(action1), action2: dispatch(action2) }
  • combineReducers:将多个reducer的对象转换成一个大的reducer 例如({ reducer1, reducer2 }) => return (preState, action) => return { reducer1: newReducer1, reducer2: newReducer2 }
  • compose:将函数数组[fn1, fn2, fn3]转换成嵌套函数(...args) => fn3(fn2(fn1(...args)))

安装redux:

npm install --save redux

基本使用

redux通过createStore创建状态容器store,返回一个对象{ getState:返回状态的函数,dispatch:(action) => 触发reducer更新state,subscribe:(listener:Function) => dispatch触发时执行listener回调 },createStore接受两个参数

第一个参数为回调函数(preState, action) => {return newState},根据上一个状态以及action返回新的状态,我们通常称这个函数为reducer,而action一般的规范是{type, ...rest},type用大写英文表示,rest为额外数据, 它与Array.prototype.reduce(callback, initValue)中的callback都是通过前一个状态以及触发改变的逻辑action获取下一个状态,当参数一致时,返回的状态也一致,所以reducer属于纯函数,所以不要添加副作用或者非纯函数在里面如API请求,Math.random()这些不确定因素

第二个参数非必选,传入用applyMiddleware包裹的中间件函数,对store的dispatch方法进行拦截加工,在执行dispatch触发状态更新前做一些额外的逻辑

import { createStore, applyMiddleware, combineReducers } from './index.js';
// 中间件redux-thunk:处理异步dispatch
import thunk from "./middlewares/thunk.js";
// 中间件redux-logger:打印state前后状态
import logger from "./middlewares/logger.js";

function reducer (state = 0, action) {
  switch (action.type) {
      case 'ADD': return ++state;
      case 'MINUS': return --state;
      default: return state;
  }
}

const store = createStore(combineReducers({ count: reducer }), applyMiddleware(thunk, logger));
store.dispatch({ type: 'ADD' })
// 异步触发reducer,需要中间件对dispatch进行封装
store.dispatch((dispatch) => {
  setTimeout(() => {
    dispatch({type: "ADD", payload: 1});
  }, 1000);
});
console.log(store.getState(), 888)

实现原理

createStore

  • 当存在enhancer,也就是中间件的时候,会走中间件的逻辑
  • 通过闭包将currentState存放到getState函数里
  • dispatch接受action,触发reducer函数,根据不同的action获取不同的状态并发布订阅的函数
  • 订阅函数listener存入数组并返回一个删除listener的函数
export default function createStore (reducer, enhancer) {
  if (enhancer) {
    return enhancer(createStore)(reducer)
  }

  let currentState;
  const currentListeners = [];

  function getState () {
    return currentState;
  }

  function dispatch (action) {
    currentState = reducer(currentState, action);
    currentListeners.map((listener) => listener());
  }

  function subscribe (listener) {
    currentListeners.push(listener);
    return () => currentListeners.splice(currentListeners.indexOf(listener), 1);
  }
  
  return {
    getState,
    dispatch,
    subscribe
  }
}

applyMiddleware

  • applyMiddleware将传入的中间件按顺序执行,通过聚合函数将他们包裹起来,形成Koa的那种洋葱圈模型,最后将封住后的dispath和其他参数返回
export default function applyMiddleware (...middlewares) {
    return (createStore) => (reducer) => {
        const store = createStore(reducer);
        let dispatch = store.dispatch;
        const midAPI = {
            getState: store.getState,
            dispatch
        };

        const chain = middlewares.map(middleware => middleware(midAPI))

        dispatch = reverseCompose(chain)(store.dispatch);

        return { ...store, dispatch }
    }
}

function reverseCompose (fns) {
  if (fns.length === 0) {
    return (arg) => arg
  }
  if (fns.length === 1) {
    return fns[0]
  }

  return fns.reduce((res, fn) => (...args) => res(fn(...args)))
}

combineReducers

  • 对传过来的多个reducers的对象封装到一个大的reducer里,每次action进行比较,返回比较后的结果
export default function (reducers) {
  return function combination (state = {}, action) {
    let nextState = {};
    let hasChanged = false;
    for (let key in reducers) {
      const reducer = reducers[key];
      nextState[key] = reducer(state[key], action);
      hasChanged = hasChanged || (nextState[key] !== state[key])
    }
    hasChanged = hasChanged || Object.keys(nextState).length !== Object.keys(state).length
    return hasChanged ? nextState : state;
  }
}

compose

  • 这个不好解释,自己悟吧
function compose (fns) {
  if (!fns.length) return () => {}
  return fns.reduce((res, fn) => (...args) => fn(res(...args)));
}

bindActionCreators

  • 这个是配合react-redux的connect用的,connect第二个参数可以接收函数对象creaters,bindActionCreators就是对creaters进行封装,给对象的每个值添加dispatch的调用
function bindActionCreator (creater, dispatch) {
  return (...args) => dispatch(creater(...args));
}

export default function (creaters, dispatch) {
  return Object.keys(creaters).reduce((res, key) => {res[key] = bindActionCreator(creaters[key], dispatch); return res;}, {})
}

中间件的实现

处理异步的中间件

next为下一个中间件函数,当action为函数时,等到异步结束调用dispatch则执行下一个中间件


function thunk({dispatch, getState}) {

    return next => action => {

        if (typeof action === "function") {

            return action(next, getState);

        }

        return next(action);

    };

}