dva中的subscriptions应该这么用

3,762 阅读2分钟

欢迎关注公众号【码上出击】,更多精彩内容敬请关注公众号最新消息。

demo地址

Q:dva中的 subscriptions 到底是干嘛用的?
A:如果你需要订阅一些数据,并且处理数据后的逻辑仅与当前model相关,那么就应该用 subscriptions 。

官方文档对于subscriptions的描述太简单了,以致很多同学对这个概念不是很清楚。

1. 代码分析

从源码中摘取出来了与subscription有关的关键代码如下:

// index.js

import { run as runSubscription, unlisten as unlistenSubscription } from './subscription';

/**
 * Create dva-core instance.
 */
export function create(hooksAndOpts = {}, createOpts = {}) {
  // ......
  
  const app = {
    _models: [prefixNamespace({ ...dvaModel })],
    _store: null,
    _plugin: plugin,
    use: plugin.use.bind(plugin),
    model,
    start,
  };
  return app;
  
  // ......
  
  /**
   * Register model before app is started.
   */
  function model(m) {
    if (process.env.NODE_ENV !== 'production') {
      checkModel(m, app._models);
    }
    const prefixedModel = prefixNamespace({ ...m });
    app._models.push(prefixedModel);
    return prefixedModel;
  }

  /**
   * Inject model after app is started.
   */
  function injectModel(createReducer, onError, unlisteners, m) {
    m = model(m);

    const store = app._store;
    store.asyncReducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions);
    store.replaceReducer(createReducer());
    if (m.effects) {
      store.runSaga(app._getSaga(m.effects, m, onError, plugin.get('onEffect'), hooksAndOpts));
    }
    if (m.subscriptions) {
      unlisteners[m.namespace] = runSubscription(m.subscriptions, m, app, onError);
    }
  }

  /**
   * Unregister model.
   */
  function unmodel(createReducer, reducers, unlisteners, namespace) {
    const store = app._store;

    // Delete reducers
    delete store.asyncReducers[namespace];
    delete reducers[namespace];
    store.replaceReducer(createReducer());
    store.dispatch({ type: '@@dva/UPDATE' });

    // Cancel effects
    store.dispatch({ type: `${namespace}/@@CANCEL_EFFECTS` });

    // Unlisten subscrioptions
    unlistenSubscription(unlisteners, namespace);

    // Delete model from app._models
    app._models = app._models.filter(model => model.namespace !== namespace);
  }

  /**
   * Start the app.
   *
   * @returns void
   */
  function start() {
    // ......

    // Run subscriptions
    const unlisteners = {};
    for (const model of this._models) {
      if (model.subscriptions) {
        unlisteners[model.namespace] = runSubscription(model.subscriptions, model, app, onError);
      }
    }

    // Setup app.model and app.unmodel
    app.model = injectModel.bind(app, createReducer, onError, unlisteners);
    app.unmodel = unmodel.bind(app, createReducer, reducers, unlisteners);
    app.replaceModel = replaceModel.bind(app, createReducer, reducers, unlisteners, onError);

    // ......
  }
}
// subscription.js

export function run(subs, model, app, onError) {
  const funcs = [];
  const nonFuncs = [];
  for (const key in subs) {
    if (Object.prototype.hasOwnProperty.call(subs, key)) {
      const sub = subs[key];
      const unlistener = sub(
        {
          dispatch: prefixedDispatch(app._store.dispatch, model),
          history: app._history,
        },
        onError,
      );
      if (isFunction(unlistener)) {
        funcs.push(unlistener);
      } else {
        nonFuncs.push(key);
      }
    }
  }
  return { funcs, nonFuncs };
}

run方法做的事情就是把model中配置的 subscriptions 遍历执行,并且将dispatch方法和history对象做为参数传给配置的每一个subscription。

从代码上我们可以看到,start方法执行时,会将app.model注册进来的所有model.subscriptions 遍历执行,并且将执行后的返回值收集到了 unlisteners[model.namespace] 中,供 app.unmodel(namespace) 时取消订阅数据源用。

如果 subscriptions 没有返回函数,调用app.unmodel时会警告。

// Run subscriptions
const unlisteners = {};
for (const model of this._models) {
  if (model.subscriptions) {
    unlisteners[model.namespace] = runSubscription(model.subscriptions, model, app, onError);
  }
}

另外,在 subscription.js 中的 run 方法中,将 prefixedDispatch(app._store.dispatch, model) 做为 dispatch 传给了 subscription 配置的方法,精简后的代码如下:

function prefixedDispatch(dispatch, model){
	return action => {
      app._store.dispatch({
        ...action, 
        type: `${model.namespace}${NAMESPACE_SEP}${action.type}`
      })
    }
}

因此,可以看出,在 subscriptions 中,只能 dispatch 当前 model 中的 reducer 和 effects 。

2. 结论

从代码中我么可以得出以下结论:

  1. subscriptions 中配置的key的名称没有任何约束,而且只有在app.unmodel的时候才有用。
  2. subscriptions 中配置的只能dispatch所在model的reducer和effects。
  3. subscriptions 中配置的函数只会执行一次,也就是在调用 app.start() 的时候,会遍历所有 model 中的 subscriptions 执行一遍。
  4. subscriptions 中配置的函数需要返回一个函数,该函数应该用来取消订阅的该数据源。

3. 代码示例

// models/Products.js

export default {
  namespace: 'products',
  
  // ......
  
  subscriptions: {
    setupxxx({ dispatch, history }) {
      // history.listen执行后会返回unlisten函数
      return history.listen(({ pathname, query }) => {
        console.log('history')
      });
    },
    i_am_just_a_name({dispatch}){
      console.log('into')
      
      function handleClick() {
        console.log('hello')
        dispatch({
          type: 'delete',
          payload: 1
        })
      }
      document.addEventListener('click', handleClick);

      // 此处返回一个函数,用来移除click事件
      return () => {
        document.removeEventListener('click', handleClick)
      }
    }
  },
	
  // ......
};