Frontend Development 8 min read

Implementing mini-async-dva: Adding async/await Support to DVA

This article examines the limitations of the original DVA framework, proposes a mini‑async‑dva fork that adds async/await support to model effects, and details its implementation—including store management, dynamic model registration, and asynchronous component loading—while providing full code comparisons.

Fulu Network R&D Team
Fulu Network R&D Team
Fulu Network R&D Team
Implementing mini-async-dva: Adding async/await Support to DVA

Background The DVA framework is widely used in React front‑end projects, but its development has stalled and its model effects rely on generators, lacking async/await support.

Why build a new wheel To improve developer experience, the author created mini‑async‑dva , a lightweight version that retains DVA’s workflow while enabling async/await in effects.

Comparison

1. Routing files

## dva
const Foo = dynamic({
    app,
    models: () => [import('./models/foo')],
    component: () => import('./pages/Foo'),
});
...
...
## mini-async-dva
import Bar from './pages/Bar';
...
...

2. Models

## dva
export default {
    namespace: 'foo',
    state: { list: [] },
    effects: {
        *fetchList({ payload }, { call }) {
            yield call(delay, 1000);
        }
    }
};
## mini-async-dva
export default {
    namespace: 'foo',
    state: { list: [] },
    effects: {
        async fetchList(payload, updateStore) {
            await delay();
        }
    }
};

3. View layer

## dva
import React from 'react';
import { connect } from 'dva';
@connect(state => state.bar)
class Bar extends React.Component { ... }
export default Bar;
## mini-async-dva
import React from 'react';
import model from '@/model';
@model('bar')
class Bar extends React.Component { ... }
export default Bar;

Implementation

1. Store management The wheel keeps Redux for state, but adds dynamic model registration via a custom action @@redux/register . Effects are stored in an effectsMap for later execution.

const effectsMap = {};
const store = createStore((state, action) => {
    const { type, payload = {} } = action;
    const { namespace, effects, initalState, updateState } = payload;
    if (type === '@@redux/register') {
        effectsMap[namespace] = effects;
        return Object.assign({}, state, { [namespace]: initalState });
    }
    if (type === '@@redux/update') {
        return Object.assign({}, state, { [namespace]: Object.assign({}, state[namespace], updateState) });
    }
    if (type.includes('/') && !type.includes('@@redux/INIT')) {
        const [sliceNameSpace, effect] = type.split('/');
        if (effectsMap[sliceNameSpace] && effectsMap[sliceNameSpace][effect]) {
            executeAsyncTask(state, sliceNameSpace, effectsMap[sliceNameSpace][effect], payload);
        }
    }
    return state;
}, {});

2. Async loading AsyncComponent loads required models asynchronously, registers them, and renders the wrapped view only after all models are loaded.

import { useStore } from 'react-redux';
function AsyncComponent({ deps, children, ...rest }) {
    const store = useStore();
    const [modelLoaded, setModelLoaded] = useState(!Array.isArray(deps) && deps.length === 0);
    useEffect(() => {
        if (!modelLoaded) {
            Promise.all(deps.map(dep => runImportTask(dep))).then(() => setModelLoaded(true));
        }
    }, []);
    function runImportTask(dep) {
        if (!store.getState().hasOwnProperty(dep)) {
            return new Promise((resolve, reject) => {
                import(`models/${dep}.js`).then(module => {
                    const { namespace, state: initalState = {}, effects } = module.default;
                    store.dispatch({
                        type: '@@redux/register',
                        payload: { effects, initalState, namespace: namespace || dep }
                    });
                    resolve();
                }).catch(reject);
            });
        }
    }
    if (modelLoaded) {
        return <>{React.createElement(children, rest)};
    }
    return null;
}

3. State binding The model decorator collects model dependencies, wraps the component with connect , and nests it inside AsyncComponent to ensure models are loaded before rendering.

function model(...deps) {
    return function wrapComponent(target) {
        const cacheRender = connect(state => {
            return deps.reduce((mapState, dep) => {
                mapState[dep] = state[dep];
                return mapState;
            }, {});
        }, null)(target);
        return props => (
{cacheRender}
);
    };
}

Conclusion The mini‑async‑dva library provides a concise solution for adding async/await support to DVA effects, simplifies routing by using native React components, and maintains dynamic model registration, with the full source available on GitHub.

frontendJavaScriptState ManagementReactasync/awaitDVA
Fulu Network R&D Team
Written by

Fulu Network R&D Team

Providing technical literature sharing for Fulu Holdings' tech elite, promoting its technologies through experience summaries, technology consolidation, and innovation sharing.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.