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 an application
const app = dva();
// Register the view
app.router(() = > <App />);
// Start the application
app.start('#root');
Copy the code
With the Model
// Create an application
const app = dva();
app.use(createLoading()) // Use plugins
/ / registered 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'}); ,}}});// Register the view
app.router(() = > <ConnectedApp />);
// Start the application
app.start('#root');
Copy the code
Dva basic principles and some key implementation
background
- 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.
- Dva is a function that returns an app object.
- 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:
- Proxy router and start methods to instantiate app objects
- Call the start method of DVA-core while rendering the view
- 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 to enhance 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;
// 3. Proxy with custom router and start methods
app.router = router;
app.start = start;
return app;
// 3.1 Bind the router passed by the user to app._router
function router(router) {
invariant(
isFunction(router),
`[app.router] router should be function, but got The ${typeof router}`,); app._router = router; }Call dVA-core's start method and render the view
function start(container) {
// Do a series of checks on the container and find the corresponding DOM node based on the container
if(! app._store) { oldAppStart.call(app); }const store = app._store;
// Expose the _getProvider interface for HMR
// ref: 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:
-
Create the app instance and expose the use, Model and start interfaces
-
Do this using the start method
-
Initialization of the storeCopy the code
-
Models and Effects package, collect and run SAGAsCopy the code
-
Run all Model.SubscriptionsCopy the code
-
Expose app.model, app.unmodel and app.replaceModel interfacesCopy the code
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; // createOpts is constructed in dva/index.js
const plugin = new Plugin(); // The plugin mechanism in DVA-core. Each instantiated DVA object contains a plugin object
plugin.use(filterHooks(hooksAndOpts)); // Convert the hooks related properties on the DVA (opTS) construction parameter opts to a plug-in
const app = {
_models: [prefixNamespace({ ...dvaModel })],
_store: null._plugin: plugin,
use: plugin.use.bind(plugin), // Expose the use method for writing custom plug-ins
model, // The exposed model method used to register the model
start, // The original start method is called by oldStart when applying render to a DOM node
};
return app;
}
Copy the code
dva-core start
Function:
- Encapsulate Models and Effects, collect and run sagAs
- Complete the initialization of the Store
- Run all Model.Subscriptions
- 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 = [];
constreducers = { ... initialReducer };for (const m of app._models) {
Merge each model into a reducer, where key is the value of the namespace and value is the reducer function
reducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions);
if (m.effects) {
// Collect each effect into the sagas array
sagas.push(app._getSaga(m.effects, m, onError, plugin.get('onEffect'), hooksAndOpts)); }}// Initialize the 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 redux-saga's createSagaMiddleware to createSagaMiddleware, and call all the collected asynchronous methods of the middleware's Run method
// The run method listens for each side effect action and executes the corresponding saga when the action occurs
sagas.forEach(sagaMiddleware.run);
// Setup app
setupApp(app);
/ / run the 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() {
returnreducerEnhancer( 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 ideas are as follows: Route jumps 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:
- The status and page logic are extracted from the reducer and become an independent store
- 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
- 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:
- Click on the Create Todo button to initiate an action of type == addTodo
- 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:
- Unify store and Saga into a model concept and write it in a JS file
- A Subscriptions service was added to collect actions from other sources such as keyboard operations
- 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({ dispatch }) {
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.
- 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 thencasually_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.
- 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
Welcome to “Byte front-end ByteFE” resume delivery email “[email protected]”