乐趣区

关于前端:dva中的subscriptions应该这么用

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

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)
      }
    }
  },
    
  // ......
};
退出移动版