redux及中间件原理实现

285 阅读9分钟

redux实现

redux是用来统一做状态管理的:

  • 所有的状态变化都要用明确的指令触发:dispatch
  • 执行状态变化的方法统一放在一个函数中处理:reducer
  • 可以订阅状态的变化,当状态变化的时候执行回调函数:subscribe

一般使用redux的方式如下:

// store.js

// 1、从redux中引入createStore
import {createStore} from 'redux'; 

// 2、创建一个reducer函数,用来处理状态的变化逻辑
function countReducer(state=0, action) {
    if (action.type === 'ADD') {
        return state + 1
    }
    
    // 处理其它type的分支
    // ...
}

// 3、调用createStore初始化store实例,并将reducer函数作为参数传进去
const store = createStore(countReducer)
export default store;

然后我们就可以在我们的文件中引入store来使用了

import React, {Component} from 'react';
import store from '../store';

export default class ReduxPage extends Component {
    componentDidMount() {
        // 订阅,当state变化的时候,重新触发执行render
        store.subscribe(() => {
            this.forceUpdate();
        });
      }
    add = () => {
        // 告诉store要执行ADD行为
        store.dispatch({type: 'ADD'})
    }
    render () {
        console.log("store", store); //sy-log
        return (
            <div>
                {/* 通过 getState获取最新的state*/}
                <div>count:{store.getState()}</div>

                {/* 点击的时候触发dispatch方法 */}
                <button onClick={this.add}>add</button>
            </div>
        )
    }
}

以上的小例子介绍了redux的简单使用方法,下面来自己动手实现一个简单的redux:

// myRedux.js

export function createStore(reducer) {
    let currentState; // 保存当前的状态
    let listeners = []; // 收集订阅函数

    function dispatch (action) {
        currentState = reducer(currentState, action); // 执行dispatch的时候根据reducer获取更新后的state
        listeners.forEach(i => i()); // 执行订阅函数
    }

    // getState直接将最新的state返回就可以了
    function getState () {
        return currentState;
    }

    // subscribe将订阅函数收集到listeners里面
    function subscribe (listener) {
        listeners.push(listener);
    }

    // 手动触发一次dispatch,让currentState获取到初始的默认值
    // 这个type取一个任意的值就行,不要与reducer里的其它type重复就可以
    dispatch({type: "@INIT/ANY VALUE"});

    return {
        dispatch,
        getState,
        subscribe
    }
}

这就实现了一个redux的简单版本了,其实它的原理真的很简单,就是派发一个action,然后让一个函数接收当前的state和派发的action作为参数,执行一下返回新的state就可以了。

中间件实现

redux的原理很简单,实现的功能也很简单,就是通过派发action,然后调用reducer函数来返回新的state。当时当我们要实现一些更复杂的功能的时候,单纯的redux就有点不够用了,所以需要引入中间件。

中间件的功能就是对redux进行增强,在原先redux的基础上再做一些额外的事情。其实现原理就是对dispatch方法进行封装,实现加强版本的dispatch。

例如我们原先执行store.dispatch({type: 'ADD'}),那么就会直接调用reducer方法的ADD的逻辑。而中间件就是对原有的dispatch进行封装,当我们在调用dispatch方法的时候,先处理一些中间件里的功能,做完中间件里的功能之后再触发真正的dispatch。

常用的中间件有redux-loggerredux-thunk,通过这两个中间件来讲解一下中间件的基本原理。

打印日志:

// redux-logger
// 在执行dispatch的时候打印日志

// 保存原始的dispatch
let originDispatch = store.dispatch;

// 对dispatch进行加强,加入打印日志功能
store.dispatch = function(action) {
    console.log('这里打印日志'); // 这里除了打印日志也可以做其它的功能
    
    // 最终还是执行原来的dispatch
    originDispatch(action);
}

// 异步请求

let originDispatch = store.dispatch;

store.dispatch = function(action) {
    model.getData()
        .then((data) => {
            action.data = data;
            originDispatch(action); // 异步请求到数据后再触发原先的dispatch
        })
}

从上面可以看出,其实中间件的最终原理就是对dispatch进行包装,先处理中间件的逻辑,处理完之后再触发真正的dispatch。

下面我们要开始动手实现真正的中间件,先来看下中间件是如何使用的:

import {createStore, applyMiddleware} from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";

// 创建一个reducer函数,用来处理状态的变化逻辑
function countReducer(state=0, action) {
    if (action.type === 'ADD') {
        return state + 1
    }
    
    // 处理其它type的分支
    // ...
}


// 重点:applyMiddleware
// 在createStore的时候,第二参数为applyMiddleware,applyMiddleware的参数为我们要使用的中间件
// 当我们使用store.dispatch的时候,会先经过thunk和logger中间件,然后再执行真正的dispatch
const store = createStore(countReducer, applyMiddleware(thunk, logger));

createStore处理第二个参数

因为现在createStore可以接收applyMiddleware作为第二个参数,我们对上面的createStore函数进行下处理,适配第二个参数:

// myRedux.js

export function createStore(reducer, enhancer) {

    // 关键代码,处理第二个参数
    // enhancer就是applyMiddleware(thunk, logger),意思就是对store进行加强
    // enhancer接收createStore作为参数,因为要对createStore进行加强,所以必须把原先的createStore传进去
    // 当然reducer也要作为参数传过去
    
    // 调用enhancer(createStore)(reducer)的时候,会在函数内部执行createStore(reducer)获得原始的store
    // 然后通过store.dispatch获得原始的dispatch,再调用中间件对dispatch进行各种加强
    
    // 这里的确有点绕啊,enhancer是作为createStore的参数的,然后又把createStore作为enhancer的参数
    // 最终调用没有enhancer参数的createStore,即createStore(reducer)来获取store,再对这个store里的dispatch进行加强
    // 真的有点骚,具体这里如何实现的看下下文的applyMiddleware实现
    if (enhancer) {
      return enhancer(createStore)(reducer);
    }
    
    
    // 下面的功能是上面文章实现的redux,可以不看...
  
    let currentState; // 保存当前的状态
    let listeners = []; // 收集订阅函数

    function dispatch (action) {
        currentState = reducer(currentState, action); // 执行dispatch的时候根据reducer获取更新后的state
        listeners.forEach(i => i()); // 执行订阅函数
    }

    // getState直接将最新的state返回就可以了
    function getState () {
        return currentState;
    }

    // subscribe将订阅函数收集到listeners里面
    function subscribe (listener) {
        listeners.push(listener);
    }

    // 手动触发一次dispatch,让currentState获取到初始的默认值
    // 这个type取一个任意的值就行,不要与reducer里的其它type重复就可以
    dispatch({type: "@INIT/ANY VALUE"});

    return {
        dispatch,
        getState,
        subscribe
    }
}

applyMiddleware实现

从上面可以看到,我们调用中间件的方式是这样的

const store = createStore(countReducer, applyMiddleware(thunk, logger));

createStore是这样的

function createStore(reducer, enhancer) {
    if (enhancer) {
      return enhancer(createStore)(reducer);
    }
    
    ...
}

所以执行applyMiddleware返回的是一个函数,接收createStore作为参数,然后执行enhancer(createStore)又返回一个函数,参数是reducer。我们先实现一个applyMiddleware,这个applyMiddleware先不做任何加强,就是让原先的逻辑可以顺利执行,如下:

function applyMiddleware(...middlewares) {
  return createStore => (reducer) => {
    // 得到原始的store,先不做任何加强,直接把该store返回回去
    // 相当于没有使用中间件
    const store = createStore(reducer);
    return {
      ...store,
  };
}

从上面代码可以看出,applyMiddleware内部还是调用原始的createStore(reducer)来获取store,把该store直接返回回去,就可以让原有的功能顺利执行,只是现在store.dispatch还没有加强,那么如何加强呢?就是要通过中间件,其基本原理如下:

function applyMiddleware(...middlewares) {
  return createStore => (reducer) => {
    const store = createStore(reducer);
    let dispatch = store.dispatch;
    
    dispatch = function () {
        // 使用middlewares对dispatch进行处理,然后返回加强版本的dispatch
        // ...
        return enhancerDispatch
    }
    return {
      ...store,
      
      // 用加强版本的dispatch覆盖store里原始的dispatch
      dispatch
  };
}

所以现在要做的就是如何使用middlewares对原始的dispatch进行加强。

中间件是一个函数,接收dispatch作为参数,然后返回一个新的dispatch。 例如logger中间件:

function logger(dispatch) {
  function enhancerDispatch = action => {
    console.log(action.type + "执行了");
    return dispatch(action);
  };
  
  return enhancerDispatch
}

如何把中间件串起来,因为中间件执行后返回的是一个加强版本的新的dispatch,我们只需要把这个新的dispatch作为参数传给下一个中间件就可以了

比如我们有3个中间,middleware1、middleware2、middleware3。

我们如何得到经过3个中间件加强的dispatch呢,只需要像下面这样做就可以了:

let finallyDispatch = middleware3(middleware2(middleware1(dispatch)))

但是像上面这么写有点不那么优雅,所以我们写一个compose函数来将所有的中间件串联成一个函数:

let finallyDispatch = compose(middleware1,middleware2,middleware3)

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg;
    // return () => {};
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  
  // 重点,这一行代码可能需要好好消化
  return funcs.reduce((a, b) => (dispatch) => a(b(dispatch)));
}

所以我们现在实现的applyMiddleware是这样的:

export function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args);
    let dispatch = store.dispatch;
    
    dispatch = compose(...middlewares)(dispatch);
    return {
      ...store,

      // 覆盖上面store里的dispatch
      dispatch
    };
  };
}

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg;
    // return () => {};
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

然后我们来实现两个中间件,redux-logger和redux-thunk

// redux-logger
function logger(dispatch) {
  function enhancerDispatch = action => {
    console.log(action.type + "执行了");
    return dispatch(action);
  };
  
  return enhancerDispatch
}

// redux-thunk
// 支持action里面传函数
// 如果action是函数,则执行action并将dispatch作为参数传进去
// dispatch可以在action函数内以异步的方式触发
function thunk(dispatch) {
  function enhancerDispatch = action => {
    // action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
    if (typeof action === "function") {
      return action(dispatch);
    } else {
      return dispatch(action);
    }
  };
  
  return enhancerDispatch
}

以上就是中间件的实现原理

上面我们的中间件函数只接收一个dispatch作为参数,然而中间件可能需要其它参数,所以我们需要在中间件外再套一层函数,以上面的thunk函数为例,如果调用action函数的时候需要传getState方法,那么getState需要作为参数传进来,如下:

function thunk ({getState}) {
    // 原先的thunk,现在变成了originThunk
    function originThunk(dispatch) {
      function enhancerDispatch = action => {
        // action 可以是对象 还可以是函数 ,那不同的形式,操作也不同
        if (typeof action === "function") {
          return action(dispatch);
        } else {
          return dispatch(action);
        }
      };
      
      return enhancerDispatch
    }
    return originThunk
}

那现在thunk什么时候执行,以及getState参数怎么传进来呢?我们得再改造一下我们的applyMiddleware方法,看下面的新增方法就可以了:

function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args);
    let dispatch = store.dispatch;
    
    
    // ========= 新增代码 ==========
    // 需要传给中间件函数的参数
    const middleApi = {
      getState: store.getState,
      dispatch
    };
    // 给middleware参数,比如说dispatch
    // 把所有中间件都先执行一遍,然后将返回的函数作为中间件
    // 根据闭包原理,那么返回的函数也可以访问其外层函数的作用域
    const middlewaresChain = middlewares.map(middleware =>
      middleware(middleApi)
    );
    // ========= 新增代码 ==========
    
    
    
    dispatch = compose(...middlewaresChain)(dispatch);
    return {
      ...store,

      // 覆盖上面store里的dispatch
      dispatch
    };
  };
}

从上面的代码可以看出,这里就是使用了闭包的作用域原理,在原先的中奖件函数(f1)外又套了一层(f2),我们执行f2,然后返回f1,那么在执行f1的时候,就可以访问f2的作用域了。

看下最终我们的中间件函数该怎么写:

// reudx-logger
function logger({getState}) {
  return dispatch => action => {
    console.log(action.type + "执行了"); //sy-log
    return dispatch(action);
  };
}

// redux-thunk
function thunk({getState}) {
  return dispatch => action => {
    if (typeof action === "function") {
      return action(dispatch, getState);
    } else {
      return dispatch(action);
    }
  };
}

applyMiddleware最终代码

function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args);
    let dispatch = store.dispatch;
    const middleApi = {
      getState: store.getState,
      dispatch
    };
    // 给middleware参数,比如说dispatch
    const middlewaresChain = middlewares.map(middleware =>
      middleware(middleApi)
    );
    dispatch = compose(...middlewaresChain)(dispatch);
    return {
      ...store,

      // 覆盖上面store里的dispatch
      dispatch
    };
  };
}

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