preface

As DvaJS says on its website, the name comes from Overwatch.

The D. VA has a powerful mecha with two fully automatic close fusion cannons, thrusters that allow the mecha to leap over enemies or obstacles, and a defense matrix that can withstand frontal long-range attacks.

The flower village in Overwatch was once the village of D.Va. It reminds me of the days when twelve D.va villages were burning with passion.

I was curious about DvaJS out of curiosity to expand the technology stack and risby’s interest in Songhana, after all, having seen some of her work before. When I collected DvaJS information on the Internet, I couldn’t think of anything unexpected.

If you are interested, you can Google it on Baidu.

So, let’s get down to business.

What is a DvaJS

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.

Key words, brief explanation

React

React is a JavaScript library for building user interfaces that use JSX to generate dynamic DOM rendering UI interfaces.

Redux 

React only has a view layer, React does data management, need to use other tools. Redux is used for global data state management. Redux is to React what Vuex is to Vue.

The Redux workflow, as shown below:

Store:

A repository for pushing data

Reducer:

Methods to help store process data (initialization, modification, deletion)

Actions:

Data update instructions

React component (UI)

Subscribe to the data in the store

If we use the library borrowing process as an example:

  • The React component is like a person who borrows books;

  • Action data update instructions, is to borrow what books;

  • Store push data warehouse, is the librarian;

  • Reducer help store data processing method, is the record table of the library, update the borrowing state of books;

Please refer to the Chinese documentation of Redux for details

Portal:

www.redux.org.cn/

React-Redux

The authors of Redux package a react-redux library dedicated to React

keywords

Provider 

connect

React-redux provides components that enable your entire app to access data in the Redux Store:

import React from "react";import ReactDOM from "react-dom";
import { Provider } from "react-redux";import store from "./store";
import App from "./App";
const rootElement = document.getElementById("root"); ReactDOM.render(<Provider store={store}>    <App />  </Provider>,  rootElement);
Copy the code

React-redux provides a connect method that lets you connect components to stores.

Normally you can call the connect method as follows:


import { login, logout } from './actionCreators'

const mapState = (state) = > state.user
const mapDispatch = { login, logout }

// first call: returns a hoc that you can use to wrap any component
const connectUser = connect(mapState, mapDispatch)

// second call: returns the wrapper component with mergedProps
// you may use the hoc to enable different components to get the same behavior
const ConnectedUserLogin = connectUser(Login)
const ConnectedUserProfile = connectUser(Profile)
Copy the code

React Redux official document

Portal:

React-redux.js.org/introductio…

redux-saga

Redux-saga is redux’s solution to asynchrony.

Redux-saga uses ES6’s Generator functionality to make asynchronous processes easier to read, write, and test.

A bit like async/await, asynchronous function names are preceded by an asterisk, which is the syntax of Generator.

The Generator returns an iterator that pauses using the yield keyword.

Much like Promise, the Generator is designed to solve problems associated with asynchronous operations.

Generator syntax, example

function *Generator(){
 var a = yield test('hhhhhhhh');
 console.log(a);
}

function test(){
    setTimeout(function(){
        console.log('halo');
    },200)}Copy the code

Example:

class UserComponent extends React.Component {...onSomeButtonClicked() {
    const { userId, dispatch } = this.props
    dispatch({type: 'USER_FETCH_REQUESTED'.payload: {userId}})
  }
  ...
}
Copy the code

This component dispatches a Plain Object action to the Store. We will create a Saga to listen for all USER_FETCH_REQUESTED actions and trigger an API call to retrieve user data.

import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '... '

// worker Saga: will be called when the USER_FETCH_REQUESTED action is dispatched
function* fetchUser(action) {
   try {
      const user = yield call(Api.fetchUser, action.payload.userId);
      yield put({type: "USER_FETCH_SUCCEEDED".user: user});
   } catch (e) {
      yield put({type: "USER_FETCH_FAILED".message: e.message}); }}FetchUser is called when each 'USER_FETCH_REQUESTED' action is dispatched to allow concurrency */
function* mySaga() {
  yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}

/* It is also possible to dispatch a USER_FETCH_REQUESTED action using takeLatest to disallow concurrency, if a USER_FETCH_REQUESTED action has already been processed. The action in the process is cancelled and only the current */ is executed
function* mySaga() {
  yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}

export default mySaga;
Copy the code

To run Saga, we need to use the Redux-Saga middleware to connect Saga to the Redux Store.

main.js

import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'

import reducer from './reducers'
import mySaga from './sagas'

// create the saga middleware
const sagaMiddleware = createSagaMiddleware()
// mount it on the Store
const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

// then run the saga
sagaMiddleware.run(mySaga)

// render the application
Copy the code

Redux-sage Chinese document

Portal:

redux-saga-in-chinese.js.org/

With all this foreplay, I finally got to play Dva, and my big knife was already hungry.

Quick learning

DvaJS official website address

Portal: dvajs.com/guide/getti…

Dva initialization

Install DVA-CLI via NPM and make sure the version is 0.9.1 or above

npm install dva-cli -g
dva -v
Copy the code

Creating a new application

dva new dva-quickstart
Copy the code

Start the

cd dva-quickstart
npm start
Copy the code

The project runs like this:

Dva routing

1. Redirect through the Link component

2. Click events to jump to

import React, { Component, Fragment } from 'react';
import { Link } from "dva/router";
import Child from '.. /components/child.js';

class userPage extends Component {
  handleToIndex = () = > {
    console.log(this.props);
    this.props.history.push('/');
    
  }
  render() {
    return (
      <Fragment>
        <div>userPage</div>
        <Link to="/">Home page</Link>
        <button onClick={this.handleToIndex}>Home page</button>
        <Child />
      </Fragment>)}}export default userPage;
Copy the code

Use withRouter to redirect the 3.com Ponents component

import React, { Component } from 'react'
import { withRouter } from 'dva/router';

class child extends Component {
  handleToIndex() {
    console.log(this.props);
    this.props.history.push('/');
  }
  render() {
    return (
      <div>
        <div>child</div>
        <button onClick={this.handleToIndex.bind(this)}>The homepage _child</button>
      </div>)}}export default withRouter(child)
Copy the code

4. The BrowserHistory of use

DvaJS uses hashHistory by default, and BrowserHistory is used to remove # signs from urls

Install the History dependency

npm install --save history
Copy the code

Modify import file


import createHistory from 'history/createBrowserHistory';
const app = dva({
  history: createHistory(),
});
Copy the code

Error:

dva Cannot find module ‘history/createBrowserHistory’

Modified to

// import createHistory from 'history/createBrowserHistory';
/ / an Error: always find module 'history/createBrowserHistory';

/ / to
import { createBrowserHistory  as createHistory} from 'history';

const app = dva({
  history: createHistory(),
});
Copy the code

Dva concept

The data flow

Data change is usually triggered by user interaction behavior or browser behavior (such as route jump, etc.). When such behavior will change data, an action can be initiated through Dispatch; if it is synchronous behavior, State can be directly changed through Reducers. If it is an asynchronous behavior (side effect), Effects will be triggered first, then flow to Reducers and finally change State. Therefore, in DVA, data flow is very clear and concise, and the idea is basically consistent with the open source community (also from the open source community).

Models optimizes redux and Redux-Saga

Dva manages the model of a domain through the concept of model, including the reducers of updating state synchronously, the effects of processing asynchronous logic, and the subscriptions to data sources.

models_reducers

In DVA, the result of the reducers aggregation is the state object of the current model. The new value (i.e., the new state) is computed with the value in the current reducers from the values passed in the Actions.

Operation example:

/ SRC /models folder

Create the indexTest.js file

export default {
  // Command space
  namespace: "indexTest"./ / state
  state: {
    name : 'Mila'
  },
  reducers: {setName(state, payLoad){
      console.log('run');
      console.log(payLoad);
      return{... state, ... payLoad.data} } } }Copy the code

In the/SRC /index.js file

Import using app.model

import dva from 'dva';
import './index.css';

// import createHistory from 'history/createBrowserHistory';
import { createBrowserHistory  as createHistory} from 'history';
// 1. Initialize
const app = dva({
  history: createHistory(),
});

// 2. Plugins
// app.use({});

// 3. Model
// app.model(require('./models/example').default);
app.model(require('./models/indexTest').default);

// 4. Router
app.router(require('./router').default);

// 5. Start
app.start('#root');
Copy the code

In the indexPage. js file in the Routes folder


import React, { Component } from 'react'
import { connect } from 'dva';

 class IndexPage extends Component {
  handleSetName = () = >{
    this.props.dispatch({
      type: 'indexTest/setName'.data: {name: 'puck'}})}render() {
    console.log(this.props);
    return (
      <div>
        IndexPage
        {this.props.msg}
        {this.props.name}
        <button onClick={this.handleSetName}>setName</button>
      </div>)}}const mapStateToProps = (state, ownProps) = > {
  console.log(state);
  return {
    msg: 'I'm a MSG'.prop: state.prop,
    name: state.indexTest.name
  }
}

export default connect(mapStateToProps)(IndexPage)
Copy the code

As shown in the code above, first introduce Connect

import { connect } from 'dva';
Copy the code

MapStateToProps, derived from Redux, is a function used to map components to store states.

const mapStateToProps = (state, ownProps) = > {
  console.log(state);
  return {
    msg: 'I'm a MSG'.prop: state.prop,
    name: state.indexTest.name
  }
}
Copy the code

When exporting, connect components with CONNECT.

export default connect(mapStateToProps)(IndexPage)
Copy the code

Execution process:

In the this.props. Dispatch () method, an object containing a type type and the data type to be dispatched is the namespace/ method name in/SRC /models/indexTest.js

For example: indexTest/elegantly-named setName

this.props.dispatch({
  type: 'indexTest/setName'.data: {name: 'puck'}})Copy the code

The reducers in Model then perform the operation to change the value of state, as shown in/SRC/Models/indextest.js:

export default {
  // Command space
  namespace: "indexTest"./ / state
  state: {
    name : 'Mila'
  },
  reducers: {setName(state, payLoad){
      console.log('run');
      console.log(payLoad);
      return{... state, ... payLoad.data} } } }Copy the code

models_effects

An Effect is called a side Effect, and in our application, asynchronous operations are the most common. It comes from the concept of functional programming, and it’s called a side effect because it makes our function impure, the same input doesn’t necessarily get the same output.

Asynchrony in Models, again based on Redux-Sage

Dva handles side effect(asynchronous tasks) by adding effects properties to model, which is implemented based on Redux-Saga with Generator syntax.

Add Effects to the Model file

effects: {
    *setNameAsync({ payLoad }, { put, call }) {
      yield put({
        type: 'setName'.data: {name: 'malena morgan'}})console.log('run'); }}Copy the code

Modify the models/ IndexTest.js file


export default {
  // Command space
  namespace: "indexTest"./ / state
  state: {
    name: 'Mila'
  },
  reducers: {
    setName(state, payLoad) {
      console.log('run');
      console.log(payLoad);
      return{... state, ... payLoad.data } } },effects: {*setNameAsync({ payLoad }, { put, call }) {
      yield put({
        type: 'setName'.data: {name: 'malena morgan'}})console.log('run'); }}}Copy the code

In the indexPage. js file in the Routes folder, add the handleSetNameAsync method for asynchronous operations.


import React, { Component } from 'react'
import { connect } from 'dva';

 class IndexPage extends Component {
  handleSetName = () = >{
    this.props.dispatch({
      type: 'indexTest/setName'.data: {name: 'puck'
      }
    })
  }
  handleSetNameAsync= () = >{
    this.props.dispatch({
      type: 'indexTest/setNameAsync'.data: {name: 'puck'}})}render() {
    console.log(this.props);
    return (
      <div>
        IndexPage
        {this.props.msg}
        {this.props.name}
        <button onClick={this.handleSetName}>setName</button>
        <button onClick={this.handleSetNameAsync}>setNameAsync</button>
      </div>)}}const mapStateToProps = (state, ownProps) = > {
  console.log(state);
  return {
    msg: 'I'm a MSG'.prop: state.prop,
    name: state.indexTest.name
  }
}

export default connect(mapStateToProps)(IndexPage)
Copy the code

models_Api

Configuring proxy Requests

Modify the. Webpackrc file in the root directory

{
  "proxy": {
    "/apis": {
      "target": "https://cnodejs.org"."changeOrigin": true."pathRewrite": {
        "^/apis": ""}}}}Copy the code

Save the modification and the project will restart.

Modify the /services/example.js file


import request from '.. /utils/request';
const proxy = "/apis/"
export function query() {
  return request('/api/users');
}

export function userInfo() {
  return request(proxy + '/api/v1/user/alsotang')}Copy the code

/utils/request.js; /utils/request.js

You can see that the FETCH is actually dVA-wrapped

import fetch from 'dva/fetch';

function parseJSON(response) {
  return response.json();
}

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
export default function request(url, options) {
  return fetch(url, options)
    .then(checkStatus)
    .then(parseJSON)
    .then(data= > ({ data }))
    .catch(err= > ({ err }));
}
Copy the code

Next, introduce the request interface place in the Routes /IndexPage.js file

import * as apis from '.. /services/example'
Copy the code

Make a request in the componentDidMount life cycle

 componentDidMount() {
    apis.userInfo().then((res) = >{
      console.log(res); })}Copy the code

Page Displays the data returned by the outbound interface. The request succeeds.

Then, the interface is invoked in Models

Modify the models/ IndexTest.js file

import * as apis from '.. /services/example'

export default {
  // Command space
  namespace: "indexTest"./ / state
  state: {
    name: 'Mila'.userInfo:{}
  },
  reducers: {
    setName(state, payLoad) {
      console.log('run');
      console.log(payLoad);
      return{... state, ... payLoad.data } },setUserInfo(state, payLoad){
      console.log(payLoad);
      return { ...state, userInfo: payLoad.data }
    }
  },
  effects: {*setNameAsync({ payLoad }, { put, call }) {
      yield put({
        type: 'setName'.data: {name: 'malena morgan'}})console.log('run');
    },
    *getUserInfo({ payLoad }, { put, call }){
      let res = yield call(apis.userInfo)
      if(res.data){
        console.log(res.data.data);
        yield put({
          type: 'setUserInfo'.data:res.data.data
        })
      }
    }
  }
}
Copy the code

Create a getUserInfo method to send the asynchronous request interface through call. After receiving the returned data, put triggers the action behavior to modify the data in state.

*getUserInfo({ payLoad }, { put, call }){
      let res = yield call(apis.userInfo)
      if(res.data){
        console.log(res.data.data);
        yield put({
          type: 'setUserInfo'.data:res.data.data
        })
      }
    }
Copy the code

Add the getUserInfo method in the Routes /IndexPage.js file and add the userInfo page in mapStateToProps to display the data.

import React, { Component } from 'react'
import { connect } from 'dva';

import * as apis from '.. /services/example'

 class IndexPage extends Component {
  handleSetName = () = >{
    this.props.dispatch({
      type: 'indexTest/setName'.data: {name: 'puck'
      }
    })
  }
  handleSetNameAsync= () = >{
    this.props.dispatch({
      type: 'indexTest/setNameAsync'.data: {name: 'puck'
      }
    })
  }
  getUserInfo = () = >{
    this.props.dispatch({
      type: 'indexTest/getUserInfo'})},componentDidMount() {
    apis.userInfo().then((res) = >{
      console.log(res); })}render() {
    console.log(this.props);
    return (
      <div>
        IndexPage
        {this.props.msg}
        {this.props.name}
        <button onClick={this.handleSetName}>setName</button>
        <button onClick={this.handleSetNameAsync}>setNameAsync</button>
        <button onClick={this.getUserInfo}>getUserInfo</button>
      </div>)}}const mapStateToProps = (state, ownProps) = > {
  console.log(state);
  return {
    msg: 'I'm a MSG'.prop: state.prop,
    name: state.indexTest.name,
    userInfo: state.indexTest.userInfo
  }
}

export default connect(mapStateToProps)(IndexPage)
Copy the code

models_subscription

The Subscription semantics are subscriptions that are used to subscribe to a data source and then dispatch required actions based on conditions. The data sources can be the current time, the server’s Websocket connection, keyboard input, geolocation changes, history route changes, and so on.

A simple demonstration of the use of Subscription takes the history route change example.

Modify it in the models/ Indextest.js file

import * as apis from '.. /services/example'

export default {
  // Command space
  namespace: "indexTest"./ / state
  state: {
    name: 'Mila'.userInfo:{}
  },
  reducers: {
    setName(state, payLoad) {
      console.log('run');
      console.log(payLoad);
      return{... state, ... payLoad.data } },setUserInfo(state, payLoad){
      console.log(payLoad);
      return { ...state, userInfo: payLoad.data }
    },
    haloFunction(state, payLoad){
      console.log('halo-reducers');
      returnstate; }},effects: {*setNameAsync({ payLoad }, { put, call }) {
      yield put({
        type: 'setName'.data: {name: 'malena morgan'}})console.log('run');
    },
    *getUserInfo({ payLoad }, { put, call }){
      let res = yield call(apis.userInfo)
      if(res.data){
        console.log(res.data.data);
        yield put({
          type: 'setUserInfo'.data:res.data.data
        })
      }
    }
  },
  subscriptions: {halo({dispatch, history}){
      console.log('halo');
      console.log(history);
      history.listen(({pathname}) = >{
        if(pathname ==='/user') {console.log('User page');
          dispatch({
            type: 'haloFunction'})}})}}}Copy the code

When the page path changes, haloFunction console prints: halo-reducers

dva.mock

First, create a new testMock.js file under the mock file

{
  "proxy": {
    "/apis": {
      "target": "https://cnodejs.org"."changeOrigin": true."pathRewrite": {
        "^/apis": ""}}}}Copy the code

Second, modify the.roadhogrc.mock.js file

export default {
  ...require("./mock/testMock")};Copy the code

Step 3: Register in services/example.js


import request from '.. /utils/request';
const proxy = "/apis/"
export function query() {
  return request('/api/users');
}

export function userInfo() {
  return request(proxy + '/api/v1/user/alsotang')}// Register the mock interface
export function mockData() {
  return request('/api/mockData')}Copy the code

It is then called in the page indexPage.js

 apis.mockData().then((res) = >{
    console.log(res);
 })
Copy the code

So that’s the simple use of DvaJS that combines documentation with practice, and the core functionality.

Finally, attach the Gitee address of my new DVA-QuickStart

Gitee.com/OrzR3/dva-q…

If you think you got something, give it a thumbs up! Click on it!