/dva

:bulb: React and redux based, lightweight and elm-style framework. (Inspired by choo)

Primary LanguageJavaScriptMIT LicenseMIT

dva

NPM version Build Status Coverage Status NPM downloads Dependencies

React and redux based, lightweight and elm-style framework. (Inspired by choo)


Table of Contents

Features

  • based on redux, redux-saga and react-router: stand on the shoulders of giants
  • small api: only 5 methods, there's not a lot to learn
  • elm cocepts: organize model with reducers, effects and subscriptions
  • mobile and react-native support: cross platform
  • dynamic model and router: split large app and load on demand
  • plugin system: make dva extendable, e.g. use dva-loading to avoid write showLoading and hideLoading hundreds of times
  • hmr support with babel-plugin-dva-hmr
  • support typescript: use dva-boilerplate-typescript for quicking start

Demos

Getting Started

This is how dva app organized, with only 5 api. View Count Example for more details.

import dva, { connect } from 'dva';

// 1. Create app
const app = dva();

// 2. Add plugins (optionally)
app.use(plugin);

// 3. Register models
app.model(model);

// 4. Connect components and models
const App = connect(mapStateToProps)(Component);

// 5. Config router with Components
app.router(routes);

// 6. Start app
app.start('#root');

You can follow Getting Started to make a Count App step by step.

Creating an App

We recommend to use dva-cli for boilerplating your app.

// Install dva-cli
$ npm install dva-cli -g

// Create app and start
$ dva new myapp
$ cd myapp
$ npm install
$ npm start

But if you like create-react-app, feel free to read Creating dva app with create-react-app.

Concepts

View Concepts for detail explain on Model, State, Action, dispatch, Reducer, Effect, Subscription, Router and Route Components.

API

app = dva(opts)

Initialize a new dva app. Takes an optional object of handlers that is passed to app.use. Besides, you can config history and initialState here.

  • opts.history: the history for router, default: hashHistory
  • opts.initialState: initialState of the app, default: {}

If you want to use browserHistory instead of hashHistory:

import { browserHistory } from 'dva/router';
const app = dva({
  history: browserHistory,
});

app.use(hooks)

Register an object of hooks on the application.

Support these hooks:

  • onError(fn): called when an effect or subscription emit an error
  • onAction(array|fn): called when an action is dispatched, used for registering redux middleware, support Array for convenience
  • onStateChange(fn): called after a reducer changes the state
  • onReducer(fn): used for apply reducer enhancer
  • onEffect(fn): used for wrapping effect to add custom behavior, e.g. dva-loading for automatical loading state
  • onHmr(fn): used for hot module replacement
  • extraReducers(object): used for adding extra reducers, e.g. redux-form needs extra form reducer

app.model(obj)

Create a new model. Takes the following arguments:

  • namespace: namespace the model
  • state: initial value
  • reducers: synchronous operations that modify state. Triggered by actions. Signature of (state, action) => state, same as Redux.
  • effects: asynchronous operations that don't modify state directly. Triggered by actions, can call actions. Signature of (action, { put, call, select }),
  • subscriptions: asynchronous read-only operations that don't modify state directly. Can call actions. Signature of ({ dispatch, history }).

put(action) in effects, and dispatch(action) in subscriptions

Send a new action to the models. put in effects is the same as dispatch in subscriptions.

e.g.

yield put({
  type: actionType,
  payload: attachedData,
  error: errorIfHave
});

or

dispatch({
  type: actionType,
  payload: attachedData,
  error: errorIfHave
});

When dispatch action inside a model, we don't need to add namespace prefix. And if ouside a model, we should add namespace separated with a /, e.g. namespace/actionType.

call(asyncFunction)

Call async function. Support promise.

e.g.

const result = yield call(api.fetch, { page: 1 });

select(function)

Select data from global state.

e.g.

const count = yield select(state => state.count);

A typical model example:

app.model({
  namespace: 'count',
  state: 0,
  reducers: {
    add(state) { return state + 1; },
    minus(state) { return state - 1; },
  },
  effects: {
    *addDelay(action, { call, put }) {
      yield call(delay, 1000);
      yield put({ type: 'add' });
    },
  },
  subscriptions: {
    // Monitor keyboard input
    keyboard({ dispatch }) {
      return key('ctrl+up', () => { dispatch({ type: 'addDelay'}); });
    },
  },
});

And another complex model example from dva-hackernews.

app.router(({ history }) => routes)

Config router. Takes a function with arguments { history }, and expects router config. It use the same api as react-router, return jsx elements or JavaScript Object for dynamic routing.

e.g.

import { Router, Route } from 'dva/routes';
app.router(({ history } => ({
  <Router history={ history }>
    <Route path="/" component={App} />
  </Router>
});

More on react-router/docs.

app.start(selector?)

Start the application. selector is optional. If no selector arguments, it will return a function that return JSX elements.

Installation

$ npm install dva

FAQ

More FAQ

Why is it called dva?

dva is a hero from overwatch. She is beautiful and cute, and dva is the shortest and available one on npm when creating it.

Which packages was dva built on?

Is it production ready?

Sure.

Does it support IE8?

No.

Does it support react-native?

Yes. Try to get started with dva-example-react-native.

Read More

License

MIT