一般我们提到store的更新都是指的state的更新,即通过dispach发送一个action,再由reducer进行处理并返回一个新的state。
但是,当我们需要在运行时动态修改state的数据结构,以及动态修改reducer时,应该怎么办呢?
事实上这样的场景并不少见,例如:在代码分离的场景下,如果需要在懒加载时更新store,就会涉及到上述问题。下面将结合dva的实现方式进行一番阐述。
dva/dynamic
dva通过dynamic方法实现懒加载,使用方法如下:
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});
内部实现原理就不细说了,可以跟代码分离中所述实现方式类似。主要关注其中的models字段,该字段即运行时需要动态注入store的model模块。dva实例是通过model()来实现state、reducer的注入,而dva实例在start()执行前后,其model方法大的实现是不同的.
start()之前的model()方法
这时,由于store还未初始化,因此model()所做的仅仅只是将传入的参数push到app._models中,其中app即dva实例,而_model则是存储各模块model的列表。具体实现如下:
function model(m) {
if (process.env.NODE_ENV !== 'production') {
checkModel(m, app._models); // 对传入的model进行校验
}
const prefixedModel = prefixNamespace({ ...m }); // 将reducer,effect等方法重命名到对应namespace下
app._models.push(prefixedModel);
return prefixedModel;
}
start()之后的model()方法
在start()中有如下一段代码:
app.model = injectModel.bind(app, createReducer, onError, unlisteners);
即对model方法进行替换,再看一下injectModel的实现:
function injectModel(createReducer, onError, unlisteners, m) {
m = model(m); // 执行此前的mode方法,将新的model参数push到__models中
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'))
);
}
if (m.subscriptions) {
unlisteners[m.namespace] = runSubscription(
m.subscriptions,
m,
app,
onError
);
}
}
那么我们新创建的reducer在哪儿呢?如何将其添加到reducers中呢?最后如何用新的new reducer替换原有的store中的old reducer呢?
首先,通过getReducer创建新的reducer并保存到store.asyncReducers对应的namespace下:
store.asyncReducers[m.namespace] = getReducer(
m.reducers,
m.state,
plugin._handleActions
);
然后通过createReducer()获取最新的reducers,具体实现为:
function createReducer() {
return reducerEnhancer(
combineReducers({
...reducers,
...extraReducers,
...(app._store ? app._store.asyncReducers : {}), // 这里!!!
})
);
}
最后通过store.replaceReducer(createReducer())实现reducer的动态替换。至此,基本的model动态注入就完成了。
unmodel方法
我们观察源码时还会发现start()中还为app增加了unmodel()方法。具体实现如下:
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);
}
首先通过delete直接进行删除store.asyncReducers和store.asyncReducers下对应namespace的key,然后通过replaceReducer更新reducer。
接下来比较巧妙,通过store.dispatch({ type: '@@dva/UPDATE' });实现整个state的更新。事实上dva在初始化时,默认会传入一个model:
const dvaModel = {
namespace: '@@dva',
state: 0,
reducers: {
UPDATE(state) {
return state + 1;
},
},
};
这里只是简单的对@@dva下的state加一处理,但由于dispatch(action)时,该action会在新的reducer中依次执行,从而实现整个store中的state树实现自动更新,是不是非常精巧呢~
最后别忘了将app._models下对应的model删除,大功告成!