This article has participated in the activity of “New person creation Ceremony”, and started the road of digging gold creation together.

preface

Dva is a data flow solution based on Redux and Redux-Saga, with additional built-in React-Router and FETCH to simplify the development experience

There are a lot of articles about DVA on the Internet, so I won’t explain how good DVA is, but the most intuitive feeling is that it is much better than Redux Mobx

I am only here to read the explanation of how to configure DVA into H5, and explain how to use it in detail

I will use a simple timer to explain how to use DVA in a specific project. If there are any mistakes, please feel free to discuss them in the comments section

gitHub: react-mobile

Online address: React-mobile-domesy

In addition, for those who are just interested in PC, I recommend a round of Ant Design Pro V5

The installation

Dva is a data flow solution based on Redux and Redux-Saga, with additional built-in React-Router and FETCH to simplify the development experience

Of course, there is no need for a complete DVA when we configure the project. We just need the following two plug-ins to work with React-Redux

Dva-core dVa-loading

Execute the command

yarn add react-redux -S

yarn add dva-core dva-loading -S

In addition, data persistence is configured, as explained below

configuration

1. Create the dva. Js

Just to give you the general idea, we need to create two methods, createApp and getDispatch

CreateApp just injects the data into the Model and sets the Store

The purpose of getDispath is to export the Dispatch and give it a method to operate on

No more words, directly on the code can ~

import { create } from 'dva-core' import createLoading from 'dva-loading' let app let store let dispatch let registered Function createApp(opt) {opt.onAction = [] app = create(opt) app.use({}) // Inject model if (! Registered) {opt.models.foreach ((model) => app.model(model))} registered = true app.start() // Set store store = app._store app.getStore = () => store app.use({ onError(err) { console.log(err) }, Dispatch = dispatch return app} export default {createApp, getDispatch() { return app.dispatch }, }Copy the code

2. Set the models

This is mainly the project to place the model, as long as the corresponding export can be

import app from './app'

export default [
  app,
]
Copy the code

3. Centralized configuration

With the above two modules ready, you can officially configure the root directory file, which is in app.jsx

Just pass all the Models to the DVA, pass the value to the Store in the Provider, and of course the Provider needs to wrap all the components in it, and that’s it

import { Provider } from 'react-redux'
import models from './models'
import { Router, dva } from './utils';

const app = dva.createApp({
  models,
})

const store = app.getStore()

const App = () => {
  return (
    <Provider store={store}>
      ...
    </Provider>
  );
}

export default App;
Copy the code

How to use

A simple introduction

Some friends have not been exposed to DVA, so I will briefly introduce it here, if you want to know more about it, you can see: DVA document

First, the data flow of DVA:

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 thinking is basically consistent with the open source community


Getting started with Redux is easy if you’ve used it before

Without further ado, let’s start the case demonstration, with a simple timer to introduce dVA specific how to use ~

Case Description (Timer)

First we need to create a timer specific model, so we create a count.js file under models and export it to index.js

To create count.js, we write down the fixed template.

We’ll just call it count and set the initial count to 0

const Count ={
  namespace: 'count',
  state: {
    count: 0, 
  },
  effects: {
  },
  reducers: {

  },
}

export default Count
Copy the code

We can borrow it and reference it in index.js

import count from './count'

export default [
  count
]
Copy the code

The next step is to directly operate ~ on the same page

We need connect links to pass data (class components can use decorators, but I won’t go into that.)

import { connect } from 'react-redux'; import { Button } from 'antd-mobile'; Const Index = ({count, dispatch}) => {return (<div style={{padding: 12}}> <div> counter: {count? .count}</div> </div> ) } export default connect(({ count }) => ({ count }))(Index);Copy the code

So our general template has been built ~

+ 1 (Basic operation)

Now, we need to click the button to increment count by 1. How do we do that?

Let’s write it out first

<Button block style={{ marginTop: 12}} color='primary' onClick={() => { dispatch({type: 'count/setAdd')}} > add 1 </Button>Copy the code

You can see that dispatch executes a type: ‘count/setAdd’,

Where count represents the count under Models and setAdd represents the method executed


We are writing count.js

const Count ={
  namespace: 'count',
  state: {
    count: 0, 
  },
  effects: {
    *setAdd({ payload }, {call, put, select}){
      yield put({
        type: 'getAdd'
      })
    },
  },
  reducers: {
    getAdd(state, payload) {
      return {
        count: state.count + 1,
      }
    },
  },
}

export default Count
Copy the code

Click on the flow, enter Effects and then go to the getAdd method of Reducers to complete the delivery

Minus one (simplify operations)

By adding one above, we can see that this method is troublesome because it has to jump from Effects to reducers, so can we directly operate reducers? Of course you can

Subtract ({type: ‘count/setSubtract’})

const Count ={ ... , reducers: { setSubtract(state, payload) { return { count: state.count - 1 } }, } } export default CountCopy the code

And you’re done

Set to 7 (with parameters)

Having covered the basics, let’s take a look at how to carry parameters

Setting method Dispatch ({type: ‘count/setNumber’, payload: 7})

const Count ={
  ...
  effects: {
    *setNumber({ payload }, {call, put, select}){
      yield put({
        type: 'getNumber',
        count: payload
      })
    },
  },
  reducers: {
    getNumber(state, payload) {
      return {
        count: payload.count
      }
    },
  }
}

export default Count
Copy the code

That will do

Asynchronous + 1 (with the back end)

So let’s talk about the most common work, so let’s try how dVA interface ~

Dispatch ({type: ‘count/asyncAdd’, payload: ‘homeList’})

You can divide the interface into server files, but for the sake of demonstration, I won’t separate them

import { Axios } from "@utils" async function getData(payload) { return await Axios({}, {url: payload.data}) } const Count ={ ... , effects: { *asyncAdd({ payload }, {call, put, select}){ const res = yield call(getData, { data: If (res){yield put({type: 'getAdd', async: true})}}}, reducers: { getAdd(state, payload) { return { count: state.count + 1, async: payload.async ? payload.async : undefined } }, } } export default CountCopy the code

Dva Model on

I believe that those who have never touched DVA and those who have just touched DVA have some questions about the code of the Model mentioned above. Why do you write it like this? What does the parameter mean

Five attributes of Model

Let’s start by introducing the five properties under Model: Namespace, State, Effects, Reducers, and Subscription

Subscription stands for subscription. Here we will not say, mainly introduced the other four ~

Namespace namespace

  • The model namespace and namespace are attributes of global state and are not supported. To create a multi-layer namespace.
  • Type string
  • A namespace is unique

State Initial data

  • State is the data layer of the entire Model
  • The application state is stored in an object tree
  • The initial state of the application is defined in the Model. That is, the global state made up of model states
  • Treating the operation as immutable data each time, ensuring that it is a new object each time and that there are no references, ensures State independence and makes it easy to test and track changes.

Side effects

  • Define an effect in key/value format. Used to handle asynchronous operations and business logic without directly modifying state.
  • Triggered by action, can trigger action, can interact with the server, can get global state data, and so on.

reducers

  • Define reducer in key/value format. For handling synchronization operations, the only place where state can be changed is triggered by action.
  • The format is :(state, action) => newState or [(state, action) => newState, enhancer]
  • It takes the arguments state and action and returns the newState, which is expressed as (state, action) => newState, so there is no autoadd. This function merges a set into a single value.
  • The concept of Reducer comes from functional programming. In DVA, reducers aggregation and accumulation result are 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.
  • The Reducer function must be pure

The effects,

Let’s look at Effect first

  effects: {
    *setAdd(data, {call, put, select}){
     
    },
  },
Copy the code

The first argument is the one that the trigger takes with it, which is easy to understand

Call, put, select, dVA call, PUT, select

put

Function: Trigger actions (i.e. go to reducers, judged by type)

Yield put ({type:'jj', //type:'xx/jj', // can be a different type, payload: data // need parameters});Copy the code

call

Purpose: For asynchronous calls, support promise

Request: request interface payload: request parameter const res = yield call(request, payload})Copy the code

select

Purpose: Get data from state

Const data = yield SELECT (state=>state.data);Copy the code

yield

In addition, there is a yield, which is used to ensure that the following code is executed after the current statement is completed

You also need to configure *

This is also written like a Generator

Async await

Pay special attention to

  • The namespace must be unique
  • Effects can trigger reducers for different modules

Data persistence

When we are done with this, we have a problem that after refreshing the page, the data with dVA will disappear

For H5, it is very likely to be forced to refresh by the user, do not agree with the small program, the small program itself is not recommended to refresh, so there is no need to add the concept of persistence in the Taro configuration

With H5, however, persistence is important when used in a browser or nested within an APP

Of course, refreshing is not recommended, but it cannot change the user’s actions, so we configure the data in the DVA to persist in the configuration layer

configuration

Plug-in: dva – model – persisit

Execute the command

yarn add dva-model-persisit

Note when introducing

Local storage dva-model-persist/lib/storage

Session storage dva – model – persist/lib/storage/session

Session storage is recommended

And it needs to be configured before app.start()

Don’t say much about direct configuration ~

import { persistEnhancer } from 'dva-model-persist';
import storage from 'dva-model-persist/lib/storage/session';

function createApp(opt) {
	...
  app.use({
    extraEnhancers: [
      persistEnhancer({
          key: 'model',
          storage
      })
    ],
  })
  ...
  return app
}
Copy the code

Once configured, reboot to see the effect

Project presentation + specific code

Demo code

import { connect } from 'react-redux'; import { Button } from 'antd-mobile'; Const Index = ({count, dispatch}) => {return (<div style={{padding: 12}}> <div> counter: {count? .count} {count? .async ? </div> <Button block style={{marginTop: 12}} color='primary' onClick={() => {type: 'count/setAdd'})}} > add 1 </Button> <Button block style={{marginTop: 12}} color='primary' onClick={() => { dispatch({type: 'Count /setSubtract'})}} > subtract 1 </Button> <Button block style={{marginTop: 12}} color='primary' onClick={() => { dispatch({type: 'count/setNumber', payload: Button block style={{marginTop: 12}} color='primary' onClick={() => {dispatch({type: 'count/asyncAdd', payload: Export default connect(({count}) => ({count}))(Index); 'homeList'})}} > </Button> </div>) export default connect(({count}) => ({count}))(Index);Copy the code

Module code

import { Axios } from "@utils" async function getData(payload) { return await Axios({}, {url: payload.data}) } const Count ={ namespace: 'count', state: { count: 0, }, effects: { *setAdd({ payload }, {call, put, select}){ yield put({ type: 'getAdd' }) }, *setNumber({ payload }, {call, put, select}){ yield put({ type: 'getNumber', count: payload }) }, *asyncAdd({ payload }, {call, put, select}){ const res = yield call(getData, { data: If (res){yield put({type: 'getAdd', async: true})}}}, reducers: { getAdd(state, payload) { return { count: state.count + 1, async: payload.async ? payload.async : undefined } }, getNumber(state, payload) { return { count: payload.count } }, setSubtract(state, payload) { return { count: state.count - 1 } }, } } export default CountCopy the code