Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

从零开始了解redux的中间件实现 #10

Open
nitroge opened this issue Jun 24, 2021 · 0 comments
Open

从零开始了解redux的中间件实现 #10

nitroge opened this issue Jun 24, 2021 · 0 comments

Comments

@nitroge
Copy link
Owner

nitroge commented Jun 24, 2021

我们先写一个极简单的createStore函数,省去了与本次文章无关的reducer参数,最初始版本,满足修改数据需求:

function createStore(initState) {
  return {
    dispatch(change) {
      console.log('origin dispatch');
      initState += change;
    },
    getState() {
      return initState;
    },
  };
}

然后就可以这么用了:

var state = createStore(528);
state.dispatch(138);
console.log(state.getState()); // 666

兴高采烈完成后,忍不住给自己打 call: 666。

老大:我们需要在dispatch的前后各打印一行日志

你想了想,这难不倒我:

var state = createStore(528);
console.log('before change');
state.dispatch(138);
console.log('after change');

老大,黑人问号??????:我们是大公司,将来我们的代码会有好几十万行,有几千上万个这样的dispatch,你确定要一个个去写?

你: 那我把createStore返回的 dispatch 代码改了吧

老大:我们 A、B、C 项目不需要打印日志,P、Q、J 需要。

你内心 OS:这老大怕是产品派来的卧底吧。

作为需求压不死的小强,你很快平静了内心:那我加个参数,决定是否打印日志吧。

老大:我们还有TMD项目只需要打印before日志而不需要after日志,我们有MMP项目需要异步产生数据,我们有...,我们还有TNNGT项目既需要 xx 功能也需要 yy 功能...

你:大佬,别说了,别说了,我改,我改还不行么。。。。

看来是时候引入中间件机制了。

思考过程:

  1. 我们的目的是要增强原有的dispatch函数
  2. 需要增加一个函数作为参数,这个函数应该可以拿到dispatchinitState
  3. 这个函数内部返回的数据结构应该与createStore一致,这样我们后续的逻辑就可以复用

所以,我们的代码大概长这样子:

function createStore(initState, middleware) {
  if (typeof middleware === 'function') {
    return middleware(createStore(initState));
  }
  return {
    dispatch(change) {
      console.log('origin dispatch');
      initState += change;
    },
    getState() {
      return initState;
    },
  };
}

依照我们想象的middleware被调用的样子,我们来实现一个记录日志的middleware

function log(store) {
  function dispatch(change) {
    console.log('before change');
    var ret = store.dispatch(change);
    console.log('after change');
    return ret;
  }
  return {
    ...store,
    dispatch,
  };
}

小试一下:

var s = createStore(10, log);
s.dispatch(5);

粘贴代码跑一下,你会发现是你预期的样子,成功了一小步,内心配合 OS:我的一小步,确是...

目前可以使用单个中间件了,如果需要同时使用多个中间件的话,我们还得继续思考,中间件的作用是改造原有dispatch,返回新的dispatch,那么,我们需要使用多个中间件的话就得把这些中间件串起来,然后将原来的dispatch作为参数传入:a(b(c(dispatch)))

首先我们需要自己实现下compose函数,这样a(b(c(dispatch)))就可以写成compose(a,b,c)(dispatch)

function compose(...fns) {
  return fns.reduce((a, b) => (...args) => a(b(...args)));
}

然后我们需要一个函数来处理多个中间件串联的情况,串联完成后再将其给到createStore作为第二个参数:

function applyMiddleware(...middlewares) {
  return function(store) {
    var dispatch = compose(...middlewares)(dispatch);
    // ...
    return {
      ...store,
      dispatch,
    };
  };
}

似乎有些问题,我们compose操作后得到的最终dispatchstore.getState无法在各个中间件内部使用,
我们需要将中间件函数变成一个三阶函数,接收{dispatch,getState}作为参数,返回一个函数是以compose的过程中产生的上一个dispatch作为参数的函数,这个函数返回一个新的dispatch,有点绕,我们直接看函数签名:

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

所以,我们的applyMiddleware需要改造:

function applyMiddleware(...middlewares) {
  return function(store) {
    var dispatch;
    var chain = middlewares.map(middleware =>
      middleware({
        ...store,
        // 形成闭包,延时取dispatch
        dispatch: (...args) => dispatch(...args),
      })
    );
    dispatch = compose(...chain)(store.dispatch);
    return {
      ...store,
      dispatch,
    };
  };
}

依据上面的中间件签名,我们重新实现下日志中间件:

function log() {
  return function(next) {
    return function(action) {
      console.log('before dispatch');
      var ret = next(action);
      console.log('after dispatch');
      return ret;
    };
  };
}

再实现一个 thunk 中间件:

function thunk({ dispatch }) {
  return function(next) {
    return function(action) {
      if (typeof action === 'function') {
        return action(dispatch);
      }
      return next(action);
    };
  };
}

我们就可以这样用了:

var s = createStore(10, applyMiddleware(log, thunk));
s.dispatch(5);
s.dispatch(function() {
  return 651;
});

// TODO 代码继续优化

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant