Some company projects use
DvaJS (D.va). To use it better, I researched and summarized notes here.
Overview
React and redux based, lightweight and elm-style framework.
First and foremost, dva is a data flow solution based on redux and redux-saga. To simplify the development experience, dva also includes built-in react-router and fetch, so it can be understood as a lightweight application framework.
dva does three main things:
- Unifies store and saga into a single “model” concept, written in one JavaScript file
- Adds Subscriptions to collect actions from other sources, such as keyboard operations
- Model syntax is very concise, similar to DSL or RoR, making coding very fast ✈️
So dva focuses on Redux‑based data flow, with built‑in react‑router and fetch.


Usage
dva.js
// products-model.js
export default {
// Key on global state
namespace: 'products',
// Initial value, here is an empty array
state: [],
// Equivalent to reducer in redux, receives action, updates state synchronously
reducers: {
'delete'(state, { payload: id }) {
return state.filter(item => item.id !== id);
},
},
subscriptions: {
}
};
// Initialize dva application
const app = dva({
initialState: {
products: [
{ name: 'dva', id: 1 },
{ name: 'antd', id: 2 },
],
},
});
// Register view
app.model(productsModel.default);
app.router(() => <ConnectedApp />);
// Start the application
app.start('#root');
Model reuse
dva-model-extend
Utility method to extend dva model.
https://github.com/dvajs/dva-model-extend
import modelExtend from 'dva-model-extend';
const human = {
state: {
stomach: null,
},
reducers: {
eat(state, { payload: food }) {
return { ...state, stomach: food };
},
},
};
const benjy = modelExtend(human, {
namespace: 'human.benjy',
state: {
name: 'Benjy',
},
});
Source
The project uses lerna for multi-package management
├── dva // Official React bindings for dva, with react-router@4.
├── dva-core // The core lightweight library for dva, based on redux and redux-saga.
├── dva-immer // Create the next immutable state tree by simply modifying the current tree
└── dva-loading // Auto loading data binding plugin for dva. :clap: You don't need to write `showLoading` and `hideLoading` any more.
Code size
JavaScript source code in /packages, statistics script
cloc . --md --exclude-dir=test --include-ext=js
| Language | files | blank | comment | code physical lines |
|---|---|---|---|---|
| JavaScript | 26 | 128 | 110 | 962 |
| ——– | ——– | ——– | ——– | ——– |
| SUM: | 26 | 128 | 110 | 962 |
- sideEffects : false
- Uses ES syntax, and exports in CommonJS format for third-party tools
The small code size suggests a lightweight abstraction.
Key functions
dva()
// packages/dva/src/index.js
export default function(opts = {}) {
const history = opts.history || createHashHistory();
const createOpts = {
initialReducer: {
router: connectRouter(history),
},
setupMiddlewares(middlewares) {
return [routerMiddleware(history), ...middlewares];
},
setupApp(app) {
app._history = patchHistory(history);
},
};
const app = create(opts, createOpts);
//
const oldAppStart = app.start;
app.router = router;
app.start = start;
return app;
...
}
model()
// packages/dva-core/src/index.js
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);
}
}
subscriptions
// packages/dva-core/src/subscription.js
export function run(subs, model, app, onError) {
const funcs = [];
const nonFuncs = [];
// Loop through and execute each subscription function
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 };
}
start()
// packages/dva/src/index.js
function start(container) {
// Allow container to be a string, then use querySelector to find the element
if (isString(container)) {
container = document.querySelector(container);
invariant(container, `[app.start] container ${container} not found`);
}
// Validate container type
invariant(
!container || isHTMLElement(container),
`[app.start] container should be HTMLElement`,
);
// Check if router is registered
invariant(app._router, `[app.start] router must be registered before app.start()`);
//
if (!app._store) {
oldAppStart.call(app);
}
const store = app._store;
// export _getProvider for HMR
// ref: https://github.com/dvajs/dva/issues/469
app._getProvider = getProvider.bind(null, store, app);
// If has container, render; else, return react component
if (container) {
render(container, store, app, app._router);
// Handle hot module replacement
app._plugin.apply('onHmr')(render.bind(null, container, store, app));
} else {
return getProvider(store, this, this._router);
}
}
plugin/use()
In dva, classes are used in only two places: dynamic component loading and plugin definitions
use(plugin) {
invariant(isPlainObject(plugin), 'plugin.use: plugin should be plain object');
const { hooks } = this;
for (const key in plugin) {
if (Object.prototype.hasOwnProperty.call(plugin, key)) {
invariant(hooks[key], `plugin.use: unknown plugin property: ${key}`);
if (key === '_handleActions') {
this._handleActions = plugin[key];
} else if (key === 'extraEnhancers') {
hooks[key] = plugin[key];
} else {
hooks[key].push(plugin[key]);
}
}
}
}
Final Thoughts
Reading the source code shows that dva’s encapsulation is relatively shallow, but it solves the logic organization problem around redux, while providing routing configuration/subscriptions and plugin extensions.

