Welcome to pay attention to the public number [code attack], more exciting content please pay attention to the latest news of the public number.

The demo address

Q: What exactly does DVA Subscriptions consist of? A: If you need to subscribe to some data and only have logic that deals with current models, then you should use subscriptions.

The description of subscriptions in the official documents was too simple, so that many students were not very clear about the concept.

1. Code analysis

The key code associated with subscription is extracted from the source code as follows:

// 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);
    }
    constprefixedModel = 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);

    / /...}}Copy the code
// 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 };
}
Copy the code

All the run method does is traverse the Subscriptions configured in the Model and pass the Dispatch method and history object as parameters to each subscription configured.

We can see from the code that all the Subscriptions to Model.model registered by app.model will be processed through the start method and the returned values will be collected into unlisteners[model.namespace]. Used to unsubscribe data sources when app.unmodel(namespace).

If subscriptions does not return a function, it will be warned when app.unmodel is called.

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

In addition, prefixedDispatch(app._store.dispatch, model) is passed as dispatch to the subscription configured method in the run method of the subscription. The condensed code looks like this:

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

Therefore, it can be seen that only reducer and effects in the current model can be dispatched in the SUBSCRIPTIONS.

Conclusion 2.

From the code we can draw the following conclusions:

  1. The names of keys configured in subscriptions have no constraints and are only useful when app.unmodel.
  2. Only the reducer and effects of the model can be dispatched in the SUBSCRIPTIONS configuration.
  3. The subscriptions function is only executed once, traversing all model subscriptions when app.start() is called.
  4. The functions configured in Subscriptions need to return a function that should be used to unsubscribe the data source.

3. Code examples

// models/Products.js

export default {
  namespace: 'products'./ /...
  
  subscriptions: {
    setupxxx({ dispatch, history }) {
      // history.listen Returns 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);

      // Return a function to remove the click event
      return () = > {
        document.removeEventListener('click', handleClick)
      }
    }
  },
	
  / /...
};

Copy the code