What is the Dva

Dva is a data flow solution based on Redux and Redux-Saga. In order to simplify the development experience, DVA also has additional built-in React-Router and FETCH, so it can also be understood as a lightweight application framework.

Problems solved by Dva

After a period of self-education or training, everyone should be able to understand the concept of Redux and recognize that this control of data flow makes applications more controllable and logic clearer. However, there is often a problem: there are too many concepts and reducer, saga, and action are all separate (split files).

  • File switchover problem. Redux projects usually need to be divided into Reducer, Action, saga, component and so on, and their sub-directory storage will cause a large cost of file switching.
  • It is not easy to organize business models (or domain models). For example, after we write a userList, to write a productList, we have to copy a lot of files.
  • -> watcher -> worker
  • Entry creation trouble. Take a look at this example of Redux Entry. In addition to creating the Redux Store, configuring the middleware, initializing the route, binding the Provider’s store, initializing the saga, Also deal with HMR from Reducer, Component, saga. This is an example of a real project using Redux, and it looks complicated.

The advantage of the Dva

  • Easy to learn and use, with only 6 apis, especially friendly to Redux users, and reduced to 0 APIS when used with UMI
  • Elm concept, organizing model through reducers, effects and Subscriptions
  • Plug-in mechanisms such as DVA-loading can handle loading state automatically without having to write showLoading and hideLoading over and over again
  • Support HMR, implement COMPONENTS, routes and Models HMR based on babel-plugin-dva-hMR

Dva disadvantage

  • The future is highly uncertain. After dva@3 proposed the plan the year before, officials barely maintained it.

  • For the vast majority of scenarios that are not particularly complex, this can currently be replaced by Hooks

Dva application scenarios

  • Service scenario: A project for which status management is required due to complex services due to multiple communication between components
  • Technical scenario: Projects written using the React Class Component

Dva core concepts

  • Data flow based on the Redux concept. The user’s interaction or browser behavior initiates an action through Dispatch. If it is synchronous behavior, it will directly change the State through Reducers; if it is asynchronous behavior (which can be called side Effects), it will first trigger Effects and then flow to Reducers and finally change the State.

  • Based on the basic concepts of Redux. Include:

    • State data, usually a JavaScript object, is treated as immutable data each time it is manipulated, ensuring that it is a new object each time and that there are no references. This ensures State independence and makes it easy to test and track changes.
    • The Action Action, a normal JavaScript object, is the only way to change State.
    • Dispatch, a function that triggers an action to change State.
    • Reducer describes how to change a pure function of data, accepting two parameters: the existing result and the incoming data from action, and calculating the new state.
    • Effects (Side Effects) The most common Side Effects are asynchronous operations. In order to control the operation of side effects, DVA introduces Redux-Sagas as the asynchronous flow control at the bottom layer. As the related concept of generator is adopted, the asynchronous writing method is changed into synchronous writing method, and effects is turned into a pure function.
    • Connect a function that binds State to View
  • Other concepts

    • Subscription, fetch data from source and dispatch required actions based on conditions, concept from ELM. The data sources can be the current time, the server’s Websocket connection, keyboard input, geolocation changes, history route changes, and so on.
    • Router the DVA instance provides the Router method to control routes, using the React-router.
    • Route Components, Components independent of data logic. Normally, Components that require connect Models are Route Components, organized in the /routes/ directory, and pure Components in the/Components/directory (Presentational Components, see component design methods).

Dva applies the simplest structure

Don’t take the Model

import dva from 'dva'; const App = () => <div>Hello dva</div>; // Create app const app = dva(); Router (() => < app />); // Start app.start('#root');Copy the code

With the Model

// Create app const app = dva(); Use (createLoading()) // Register Model app. Model ({namespace: 'count', state: 0, reducers: { add(state) { return state + 1 }, }, effects: { *addAfter1Second(action, { call, put }) { yield call(delay, 1000); yield put({ type: 'add' }); ,}}}); Router (() => <ConnectedApp />); // Start app.start('#root');Copy the code

Dva basic principle and some key implementation

background

  1. The whole DVA project is managed by Lerna. Find the corresponding entry file of the module in package.json of each package, and then check the corresponding source code.
  2. Dva is a function that returns an app object.
  3. At present, dVA source code core consists of two parts, DVA and DVA-core. The former implements the View layer with the high-order component React-Redux, while the latter solves the Model layer with Redux-Saga.

dva

Dva does three important things:

  1. Proxy router and start methods to instantiate app objects
  2. Call the start method of DVA-core while rendering the view
  3. Use react-redux to connect react to redux.
// dva/src/index.js export default function (opts = {}) { // 1. Initialize router and history with connect-react-router and history // add redux's middleware react-redux-router Strengthen the function of the history object const history = opts. History | | createHashHistory (); const createOpts = { initialReducer: { router: connectRouter(history), }, setupMiddlewares(middlewares) { return [routerMiddleware(history), ...middlewares]; }, setupApp(app) { app._history = patchHistory(history); }}; // 2. Call the create method in dvA-core to instantiate an app object. const app = create(opts, createOpts); const oldAppStart = app.start; Router = router; // router = router; app.start = start; return app; Random (router) {random (router) {random (router); `[app.router] router should be function, but got ${typeof router}`, ); app._router = router; Function start(container) {// Do a series of checks on the container and find the corresponding DOM node according to the container if (! app._store) { oldAppStart.call(app); } const store = app._store; // expose the _getProvider interface for HMR https://github.com/dvajs/dva/issues/469 app._getProvider = getProvider.bind(null, store, app); // Render the view if (container) {render(container, store, app, app._router); app._plugin.apply('onHmr')(render.bind(null, container, store, app)); } else { return getProvider(store, this, this._router); } } } function getProvider(store, app, router) { const DvaRoot = extraProps => ( <Provider store={store}>{router({ app, history: app._history, ... extraProps })}</Provider> ); return DvaRoot; } function render(container, store, app, router) { const ReactDOM = require('react-dom'); // eslint-disable-line ReactDOM.render(React.createElement(getProvider(store, app, router)), container); }Copy the code

We can also see that the app is initialized with create(opts, createOpts), where OPts is the configuration exposed to the user and createOpts is the configuration exposed to the developer. The real create method is implemented in DVA-core

dva-core

Dva-core completes the core functions:

  1. Create the app instance and expose the use, Model and start interfaces

  2. Do this using the start method

    1. Initialization of the store
    2. Models and Effects package, collect and run SAGAs
    3. Run all Model.Subscriptions
    4. Expose app.model, app.unmodel and app.replaceModel interfaces

dva-core create

Function: Complete the construction of app instance, and expose the use, Model and start interfaces

// dva-core/src/index.js const dvaModel = { namespace: '@@dva', state: 0, reducers: { UPDATE(state) { return state + 1; ,}}}; export function create(hooksAndOpts = {}, createOpts = {}) { const { initialReducer, setupApp = noop } = createOpts; Const plugin = new plugin (); // createOpts const plugin = new plugin (); Plugin.use (filterHooks(hooksAndOpts)); Const app = {_models: [prefixNamespace({...dvaModel})], _store: null, _plugin: plugin, use: Plugin.use.bind (plugin), // Expose the use method to make it easy to write custom plug-in model, // expose the model method to register model start, // original start method, } via oldStart when applying render to DOM nodes; return app; }Copy the code

dva-core start

Function:

  1. Encapsulate Models and Effects, collect and run sagAs
  2. Complete the initialization of the Store
  3. Run all Model.Subscriptions
  4. Expose app.model, app.unmodel and app.replaceModel interfaces
function start() { const sagaMiddleware = createSagaMiddleware(); const promiseMiddleware = createPromiseMiddleware(app); app._getSaga = getSaga.bind(null); const sagas = []; const reducers = { ... initialReducer }; For (const m of app._models) {// Merge each reducer into a reducer, Value is the reducer function reducers[m.namespace] = getReducer(m.rut, m.state, plugin._handleActions); Push (app._getsaga (m.ffects, m, onError, plugin.get('onEffect')), hooksAndOpts)); } // Initialize Store app._store = createStore({reducers: createReducer(), initialState: hooksAndOpts.initialState || {}, plugin, createOpts, sagaMiddleware, promiseMiddleware, }); const store = app._store; // Extend store store.runSaga = sagaMiddleware.run; store.asyncReducers = {}; // Execute listeners when state is changed const listeners = plugin.get('onStateChange'); for (const listener of listeners) { store.subscribe(() => { listener(store.getState()); }); } // Run sagas, call the createSagaMiddleware of redux-saga to createSagaMiddleware, call the Run method of middleware to collect all asynchronous methods // Run method to listen for each side effect action, When an action occurs, execute the corresponding saga sagas.foreach (sagamiddleware.run); // Setup app setupApp(app); // Run subscriptions const unlisteners = {}; for (const model of this._models) { if (model.subscriptions) { unlisteners[model.namespace] = runSubscription(model.subscriptions, model, app, onError); }} // Expose three model-related interfaces, Setup app.model and app.unmodel app.model = injectModel.bind(app, createReducer, onError, unlisteners); app.unmodel = unmodel.bind(app, createReducer, reducers, unlisteners); app.replaceModel = replaceModel.bind(app, createReducer, reducers, unlisteners, onError); /** * Create global reducer for redux. * * @returns {Object} */ function createReducer() { return reducerEnhancer( combineReducers({ ... reducers, ... extraReducers, ... (app._store ? app._store.asyncReducers : {}), }), ); }}}Copy the code

routing

In the previous dvA. start method we saw createOpts and saw that the corresponding method was called at different times in the start of DVA-core.

import * as routerRedux from 'connected-react-router'; const { connectRouter, routerMiddleware } = routerRedux; const createOpts = { initialReducer: { router: connectRouter(history), }, setupMiddlewares(middlewares) { return [routerMiddleware(history), ...middlewares]; }, setupApp(app) { app._history = patchHistory(history); }};Copy the code

Where initialReducer and setupMiddlewares are called when the store is initialized before setupApp is called

ConnectRouter and routerMiddleware both use the Connect-React-Router library. The main idea is that route hops are also treated as a special action.

Differences between Dva and React, React-Redux, and Redux-Saga

Native to the React

According to React official guidelines, if multiple components interact with each other, state (i.e., data) is maintained on the smallest convention parent of those components, i.e

And does not maintain any state of its own, but is passed to props by the parent node to determine its presentation. It is a Pure function in the form of: Pure Component

React-Redux

There are several obvious improvements compared to the figure above:

  1. The status and page logic are extracted from the reducer and become an independent store
  2. Both are Pure components, and it’s easy to attach a wrapper to them with the connect method to establish a connection with the Store: You can use Dispatch to inject actions into the Store to force changes in the store’s state, subscribe to changes in the store’s state, and refresh the connected component once the state changes
  3. The process of sending an action to a store using dispatch can be intercepted, which makes it a natural place to add Middleware for custom functionality, eg: logging

In this way, each part does its job, with lower coupling, higher reuse and better scalability.

Redux-Saga

Because we can use Middleware to block actions, making it easy to use asynchronous networking, we can use the Middleware library Redux-Saga, for example:

  1. Click on the Create Todo button to initiate an action of type == addTodo
  2. Saga intercepts this action and initiates an HTTP request. If the request is successful, continue to send an action type == addTodoSucc to reducer, indicating that the reducer has been created successfully. Otherwise, send the action type == addTodoFail

Dva

Dva is based on the best practices of React + Redux + Saga, and contributes to improving the coding experience in three ways:

  1. Unify store and Saga into a model concept and write it in a JS file
  2. A Subscriptions service was added to collect actions from other sources such as keyboard operations
  3. Written as a minimalist DSL (Domain specific Language), the Model makes programming more immersive and thus more efficient

Convention greater than Configuration

app.model({ namespace: 'count', state: { record: 0, current: 0, }, reducers: { add(state) { const newCurrent = state.current + 1; return { ... state, record: newCurrent > state.record ? newCurrent : state.record, current: newCurrent, }; }, minus(state) { return { ... state, current: state.current - 1}; }, }, effects: { *add(action, { call, put }) { yield call(delay, 1000); yield put({ type: 'minus' }); }}, subscriptions: {keyboardWatcher ({beat} {key (' ⌘ + up and CTRL + up, () = > {dispatch ({type: 'add'})}); ,}}});Copy the code

The idea behind Dva is worth learning

The Dva API references Choo, and the concept comes from ELM.

  1. Choo’s philosophy: Programming should be fun and easy, and apis should look simple and easy to use.

We believe programming should be fun and light, not stern and stressful. It’s cool to be cute; using serious words without explaining them doesn’t make for better results – if anything it scares people off. We don’t want to be scary, we want to be nice and fun, and then casually be the best choice around. Real casually.

We believe frameworks should be disposable, and components recyclable. We don’t want a web where walled gardens jealously compete with one another. By making the DOM the lowest common denominator, switching from one framework to another becomes frictionless. Choo is modest in its design; we don’t believe it will be top of the class forever, so we’ve made it as easy to toss out as it is to pick up.

We don’t believe that bigger is better. Big APIs, large complexities, long files – we see them as omens of impending userland complexity. We want everyone on a team, no matter the size, to fully understand how an application is laid out. And once an application is built, we want it to be small, performant and easy to reason about. All of which makes for easy to debug code, better results and super smiley faces.

  1. Concept from Elm:
  • Subscription, get data from a source, which can be the current time, websocket connection to the server, keyboard input, geolocation changes, history route changes, and so on.

The appendix

Why dva and what’s dva

Development and selection of front-end application architecture of Alipay

React + Redux best practices

Dva concept

An introduction to Dva

Dva source code analysis

Dva source code implementation

Dva source code analysis