dva-源码解析-下

首先这是一篇找妈妈的故事, model中的state,reducers,effects,是如何找到各自的妈妈呢?23333~

dva 是对redux、react-redux、react-router、redux-saga的整合,所以在看dva源码之前建议先要熟悉这些库的用法。 废话不多说,那我们就开始。先看一下生成的 index.js 文件,在这里我加入了dva-loading,用来分析plugin。

import dva from 'dva';
import createLoading from 'dva-loading';

// 1. Initialize
const app = dva();

// 2. Plugins
app.use(createLoading());

// 3. Model
app.model(require('./models/example').default);

// 4. Router
app.router(require('./router').default);

// 5. Start
app.start('#root');
复制代码

可以看到,dva生成app实例,并且绑定plugin、models、router,最后启动项目。

先去看一下dva的源码,位置是dva/src/index.js

export default function (opts = {}) {
  const history = opts.history || createHashHistory();
  const createOpts = {
    initialReducer: {
      routing,
    },
    setupMiddlewares(middlewares) {
      return [
        routerMiddleware(history),
        ...middlewares,
      ];
    },
    setupApp(app) {
      app._history = patchHistory(history);
    },
  };

  const app = core.create(opts, createOpts);
  const oldAppStart = app.start;
  app.router = router;
  app.start = start;
  return app;

  function router(router) {
    app._router = router;
  }

  function start(container) {
    // 允许 container 是字符串,然后用 querySelector 找元素
    if (isString(container)) {
      container = document.querySelector(container);
    }

    if (!app._store) {
      oldAppStart.call(app);
    }
    const store = app._store;

    // If has container, render; else, return react component
    if (container) {
      render(container, store, app, app._router);
      app._plugin.apply('onHmr')(render.bind(null, container, store, app));
    } else {
      return getProvider(store, this, this._router);
    }
  }
}
复制代码

上面代码重点在两句

  • const app = core.create(opts, createOpts); 利用dva-core生成数据层app
  • app.start = start; 由于dva-core仅仅是数据层,所以这里需要代理start方法,建立view层,比如react、router

接下来我想会重点分析model方法(这是整个dva的核心部分) 位置是dva-core/index.js

export function create(hooksAndOpts = {}, createOpts = {}) {
  const { initialReducer, setupApp = noop } = createOpts;

  const plugin = new Plugin();
  plugin.use(filterHooks(hooksAndOpts));

  const app = {
    _models: [prefixNamespace({ ...dvaModel })],
    _store: null,
    _plugin: plugin,
    use: plugin.use.bind(plugin),
    model,
    start,
  };
  return app;

  // 注册model,将model里的reducers与effects方法增加namespace,并且保存在app._models中
  function model(m) {
    if (process.env.NODE_ENV !== 'production') {
      checkModel(m, app._models);
    }
    const prefixedModel = prefixNamespace({ ...m });
    app._models.push(prefixedModel);
    return prefixedModel;
  }
}
复制代码

在一个项目中我们会注册多个model,现在models都已经被存在了app_models中。dva是如何将每个model里的state,reducers, effects, 放在redux与redux-saga中的呢?答案就在 app.start 里。start里主要是redux和redux-saga的初始化

const reducers = { ...initialReducer };
for (const m of app._models) {
  reducers[m.namespace] = getReducer(
    m.reducers,
    m.state,
    plugin._handleActions
  );
  if (m.effects)
    sagas.push(app._getSaga(m.effects, m, onError, plugin.get('onEffect')));
}
复制代码

上面代码遍历models分别将reducers和effects存下来,先看一下reducer的处理

export default function getReducer(reducers, state, handleActions) {
  // Support reducer enhancer
  // e.g. reducers: [realReducers, enhancer]
  if (Array.isArray(reducers)) {
    return reducers[1](
      (handleActions || defaultHandleActions)(reducers[0], state)
    );
  } else {
    return (handleActions || defaultHandleActions)(reducers || {}, state);
  }
}
复制代码

一般情况下都不会是数组,话说看到这里我才知道原来还可以加一个enhancer。在去看一下defaultHandleActions

function handleActions(handlers, defaultState) {
  const reducers = Object.keys(handlers).map(type =>
    handleAction(type, handlers[type])
  );
  const reducer = reduceReducers(...reducers);
  return (state = defaultState, action) => reducer(state, action);
}
复制代码

在这里遍历了reducers里的每一个key值,并把一个models下的reduers合并为一个。

function handleAction(actionType, reducer = identify) {
  return (state, action) => {
    const { type } = action;
    invariant(type, 'dispatch: action should be a plain Object with type');
    if (actionType === type) {
      return reducer(state, action);
    }
    return state;
  };
}
复制代码

关键在于 if (actionType === type), 判断类型不同就返回state,类似于 switch...case。 下面看一下合并reducer的函数

function reduceReducers(...reducers) {
  return (previous, current) =>
    reducers.reduce((p, r) => r(p, current), previous);
}
复制代码

这里的合并方式我是有点不太理解的, 这里采用的是reducer1 -> reducer2 -> reducer3,假如有一个action,models里的handleAction要全跑一遍。 为什么不直接找到相应键值的reducer运行呢?,reducer已经分析完了然后就是effects

export default function getSaga(effects, model, onError, onEffect) {
  return function*() {
    for (const key in effects) {
      if (Object.prototype.hasOwnProperty.call(effects, key)) {
        const watcher = getWatcher(key, effects[key], model, onError, onEffect);
        const task = yield sagaEffects.fork(watcher);
        yield sagaEffects.fork(function*() {
          yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`);
          yield sagaEffects.cancel(task);
        });
      }
    }
  };
}
复制代码

fork每一个effect,然后是 getWatcher 函数

// onEffect就是plugin中的OnEffect, 就如开头的那个例子dva-loading中就有一个onEffect
function getWatcher(key, _effect, model, onError, onEffect) {
  // 这里给每一个effect增加了开始、结束的事件和错误处理,并且__dva_resolve, __dva_reject是PromiseMiddleware的参数。
  // PromiseMiddleware的作用是当你dispatch effect时,返回一个promise。this.props.dispatch.then这样的写法你一定熟悉。
  function* sagaWithCatch(...args) {
    const { __dva_resolve: resolve = noop, __dva_reject: reject = noop } =
      args.length > 0 ? args[0] : {};
    try {
      yield sagaEffects.put({ type: `${key}${NAMESPACE_SEP}@@start` });
      // createEffects 封装了redux-saga的effects,主要是改变了type值,这就是你为什么可以在当前model里省略namespace的原因
      const ret = yield effect(...args.concat(createEffects(model)));
      yield sagaEffects.put({ type: `${key}${NAMESPACE_SEP}@@end` });
      resolve(ret);
    } catch (e) {
      onError(e, {
        key,
        effectArgs: args,
      });
      if (!e._dontReject) {
        reject(e);
      }
    }
  }

  // 如果插件中有onEffects,则再封装一层
  const sagaWithOnEffect = applyOnEffect(onEffect, sagaWithCatch, model, key);

  switch (type) {
    case 'watcher':
      return sagaWithCatch;
    case 'takeLatest':
      return function*() {
        yield takeLatest(key, sagaWithOnEffect);
      };
    case 'throttle':
      return function*() {
        yield throttle(ms, key, sagaWithOnEffect);
      };
    default:
      return function*() {
        yield takeEvery(key, sagaWithOnEffect);
      };
  }
}
复制代码

到目前为止我们已经处理好了reduer与effects,接下来就是redux与redux-saga的初始化工作了 首先是redux

const store = (app._store = createStore({
  // eslint-disable-line
  reducers: createReducer(),
  initialState: hooksAndOpts.initialState || {},
  plugin,
  createOpts,
  sagaMiddleware,
  promiseMiddleware,
}));
复制代码

redux-saga

sagas.forEach(sagaMiddleware.run);
复制代码

ola, dva的源码差不多就到这里了,再去看一个dva-loading是如何工作的。 先想一下dva-loading作用是监听effects的开始和结束,并把状态存在store里,那么他肯定需要一个reducer用来存储状态,和effect用来抛出action

const initialState = {
    global: false,
    models: {},
    effects: {},
};

const extraReducers = {
  [namespace](state = initialState, { type, payload }) {
    const { namespace, actionType } = payload || {};
    let ret;
    switch (type) {
      case SHOW:
        ret = {
          ...state,
          global: true,
          models: { ...state.models, [namespace]: true },
          effects: { ...state.effects, [actionType]: true },
        };
        break;
      case HIDE: // eslint-disable-line
        res = {...}
        break;
      default:
        ret = state;
        break;
    }
    return ret;
  },
};

function onEffect(effect, { put }, model, actionType) {
  const { namespace } = model;
  if (
      (only.length === 0 && except.length === 0)
      || (only.length > 0 && only.indexOf(actionType) !== -1)
      || (except.length > 0 && except.indexOf(actionType) === -1)
  ) {
      return function*(...args) {
          yield put({ type: SHOW, payload: { namespace, actionType } });
          yield effect(...args);
          yield put({ type: HIDE, payload: { namespace, actionType } });
      };
  } else {
      return effect;
  }
}
复制代码

onEffect在之前getWatcher中已经讲过,而extraReducers最后会通过combineReducers合并

总结

dva的源码已经分析完了,看完这篇文章相信你已经对dva的工作方式有了大致的了解。下期我会分享react源码。

转载于:https://juejin.im/post/5bbaa6086fb9a05d151cbb4f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值