先看下官方定义
Subscriptions 是一种从源获取数据的方法,它来自于 elm。
Subscription 语义是订阅,用于订阅一个数据源,然后根据条件 dispatch 需要的 action。数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等。
1 2 3 4 5 6 7 8 9 10
| import key from 'keymaster'; ... app.model({ namespace: 'count', subscriptions: { keyEvent(dispatch) { key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); }, } });
|
subscriptions要求内部所有成员都是函数.
疑惑1:subscriptions里面都可以写什么
Q: 官方例子中是监听了按键事件,除此之外还可以些什么。
A: 任意自定义函数
dva-core/src/checkModel.js
中可以清楚看到:
code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| export default function checkModel(model, existModels) { if (subscriptions) { invariant( isPlainObject(subscriptions), `[app.model] subscriptions should be plain object, but got ${typeof subscriptions}`, ); invariant( isAllFunction(subscriptions), `[app.model] subscription should be function`, ); } }
|
疑惑2:subscriptions里的函数什么时候会被执行
首先subscriptions是定义在model里的,而一般加载model的方法是:
1 2 3 4 5 6 7 8 9
| const app = dva({ history: browserHistory, onError(error) { } }); app.model(require('./models/app'));
|
所以看下dva中调用model(...)
方法时做了什么:
code
1 2 3 4 5 6
| function model(m) { if (process.env.NODE_ENV !== 'production') { checkModel(m, app._models); } app._models.push(prefixNamespace(m)); }
|
可以看到基本就是处理了一下(加上命名空间前缀)后,保存在了内部的_models
数组中。
同时可以注意到,dva在生产环境中是不做模型检查的,可以提高一点儿效率,但是在开发测试环境中是开启的,便于debug
真正执行Subscriptions的地方在app.start()
中
code
1 2 3 4 5 6 7 8 9 10 11 12
| function start() { const unlisteners = {}; for (const model of this._models) { if (model.subscriptions) { unlisteners[model.namespace] = runSubscription(model.subscriptions, model, app, onError); } } }
|
runSubscription
方法定义在 code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 }; }
|
具体的调用代码如下:
1 2 3 4
| const unlistener = sub({ dispatch: prefixedDispatch(app._store.dispatch, model), history: app._history, }, onError);
|
所以subscription的方法签名为({dispatch, history}, onError)
。
如果subscription的返回值是function
,则认为是unlistener
在model从app卸载时会被调用。
在app.start
之后,还可以动态注入model
,这时候model
里面的subscription还是会被执行的。
首先看:code
1 2 3 4 5 6 7
| function start(){ app.model = injectModel.bind(app, createReducer, onError, unlisteners); app.unmodel = unmodel.bind(app, createReducer, reducers, unlisteners); }
|
在app启动后,运行期间,再次调用app.model(...)
其实就是调用injectModel
:
code
1 2 3 4 5 6 7 8 9
| function injectModel(createReducer, onError, unlisteners, m) { model(m); if (m.subscriptions) { unlisteners[m.namespace] = runSubscription(m.subscriptions, m, app, onError); } }
|