Recently, I found an RN+TS imitation Himalayan project. When I saw several sections of the dva live in Bengbu, I went to find an online class and write a blog to summarize it
I What is Dva
dva = React-Router + Redux + Redux-saga
II install
1. Install the DVA cli npm install dva-cli -g 2. Connect to the directory of installation items cd ylz_project/my_reactdemo 3. Create project: DVA test project name dva new Dva-test 4. Enter the project cd Dva-test 5. Start the project npm start

III Project structure
├── /dist/ // Package destination directory ├── /src/ // Project source directory │ ├── /components/ // General component catalog │ ├── /models/ // data model │ └── example.js // Model example: the model of DVA is a collection of abstract concepts such as reducer, store and asynchronous action in redux. │ ├── /services/ // Store service related components or functions │ ├── /mock/ // Mock (false data for testing when the back end does not give an interface) │ ├── /routes/ // Page corresponding to route │ └── page.js // Page components matching routing rules │ ├── index.css // Project entry css │ ├── index.js // Project entry: manually configure the modules developed during development │ └── router.js // Project routing (the HashRouter in the react router is used by default, so you will see a # number at the end of the URL. You can disable the react router by using the DVA no router) ├── package.json // Project dependency information ├── .eslintrc // Eslint configuration ├── .gitignore // git ignores files and directories └── .webpackrc // roadhog configuration └── README.md // Development documentation
IV Dva concept
This part is mainly from official documents, so I copied it directly
Data flow direction
Data change is usually triggered by user interaction behavior or browser behavior (such as route jump). When such behavior will change data, an action can be initiated through dispatch. If it is synchronous behavior, the State will be changed directly through Reducers. If it is asynchronous behavior (side Effects) will trigger Effects first, then flow to Reducers, and finally change the State. Therefore, in the dva, the 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
Model is undoubtedly the most important concept in dva. Model here refers to domain model, which is used to aggregate data related logic. Almost all data and logic are processed and distributed here
State
type State = any
State represents the state data of the Model, which is usually represented as a javascript object (of course, it can be any value); each time when operating, it should be treated as immutable data to ensure that each time it is a new object without reference relationship, so as to ensure the independence of the state and facilitate testing and tracking changes.
In dva, you can use the instance properties of dva_ You can see the state data at the top of the store, but you rarely use it:
const app = dva(); console.log(app._store); // state data at the top
Action
type AsyncAction = any
Action is an ordinary javascript object, which is the only way to change the State. Whether the data obtained from UI events, network callbacks, WebSocket s and other data sources will eventually call an action through the dispatch function to change the corresponding data. The action must have a type attribute to indicate the specific behavior. Other fields can be customized. If you want to initiate an action, you need to use the dispatch function; It should be noted that the dispatch is passed in through props after the component connects models.
dispatch({ type: 'add', });
dispatch function
type dispatch = (a: Action) => Action
dispatching function is a function used to trigger action. Action is the only way to change the State, but it only describes one behavior. Dispatch can be regarded as the way to trigger this behavior, and Reducer describes how to change the data.
In dva, the components of connect Model can access dispatch through props, and can call Reducer or Effects in the Model. Common forms are as follows:
dispatch({ type: 'user/add', // If you call outside the model, you need to add a namespace payload: {}, // Information to be transmitted });
Reducer
type Reducer<S, A> = (state: S, action: A) => S
The Reducer (also known as reducing function) function accepts two parameters: the result of the previous accumulated operation and the value to be accumulated. It returns a new accumulated result. This function merges a collection into a single value.
The concept of Reducer comes from functional programming. Many languages have reduce API s. As in javascript:
[{x:1},{y:2},{z:3}].reduce(function(prev, next){ return Object.assign(prev, next); }) //return {x:1, y:2, z:3}
In dva, the result of aggregating and accumulating reducers is the state object of the current model. The new value (i.e. new state) is obtained by calculating the value passed in actions with the value in the current reducers. It should be noted that the Reducer must be Pure function Therefore, the same input must get the same output, and they should not have any side effects. Also, every calculation should use immutable data , the simple understanding of this feature is that each operation returns a new data (independent and pure), so the functions of thermal overload and time travel can be used.
Effect
Effect is called a side effect. In our application, the most common is asynchronous operation. It comes from the concept of functional programming. It is called side effect because it makes our function impure. The same input does not necessarily get the same output.
In order to control the operation of side effects, the bottom layer of dva introduces redux-sagas Do asynchronous process control, due to the use of Related concepts of generator Therefore, asynchronous is converted to synchronous writing, and effects is converted to pure functions. As for why we are so obsessed with pure functions, if you want to know more, you can read it Mostly adequate guide to FP Or its Chinese translation JS functional programming guide.
Subscription
Subscriptions is a way to get data from a source, which comes from elm.
Subscription semantics is subscription, which is used to subscribe to a data source and then dispatch the required action s according to conditions. The data source can be the current time, the websocket connection of the server, the keyboard input, the change of geography, the change of history route, and so on.
import key from 'keymaster'; ... app.model({ namespace: 'count', subscriptions: { keyEvent({dispatch}) { key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) }); }, } });
Router
The routing here usually refers to the front-end routing. Since our application is usually a single page application, we need the front-end code to control the routing logic through the browser History API You can monitor the changes of browser url to control routing related operations.
The dva instance provides a router method to control routing, using react-router.
import { Router, Route } from 'dva/router'; app.router(({history}) => <Router history={history}> <Route path="/" component={HomePage} /> </Router> );
Route Components
stay Component design method In, we mentioned Container Components. In dva, we usually restrict them to Route Components, because in dva, we usually design Container Components based on page dimension.
Therefore, in dva, the components that need to connect Model are usually Route Components, which are organized in the / routes / directory, while the / components / directory is pure components (presentation components).
V Dva API
app = dva(opts)
Create an application and return the dva instance. (Note: dva supports multiple instances)
opts includes:
- History: the history specified for routing. The default is hashHistory
- initialState: Specifies the initial data. The priority is higher than the state in the model. The default is {}
If you want to configure history as browserHistory, you can:
import createHistory from 'history/createBrowserHistory'; const app = dva({ history: createHistory(), });
In addition, for ease of use, opts can also be equipped with all hooks , the following contains all configurable attributes:
const app = dva({ history, initialState, onError, onAction, onStateChange, onReducer, onEffect, onHmr, extraReducers, extraEnhancers, });
There's too much left to copy Here you can see the official documents
Vi Small example


framework

components/child.jsx
import React, { Component } from "react"; import { withRouter } from "react-router"; class Child extends Component { handleToIndex = () => { this.props.history.push("/"); }; render() { return ( <div> <div>I am a general component</div> <button onClick={this.handleToIndex}>home page_child</button> </div> ); } } export default withRouter(Child);
models/indexTest.js
import * as apis from "../services/example"; export default { //Namespace namespace: "indexTest", state: { name: "Msea", }, reducers: { setName(state, payLoad) { //You need to change the address in react to detect it let _state = JSON.parse(JSON.stringify(state)); _state.name = payLoad.data.name; return _state; }, setCnodeDataList(state, payLoad) { let _state = JSON.parse(JSON.stringify(state)); _state.cnodeData = payLoad.data; return _state; }, testPath(state, payLoad) { console.log("User page"); return state; }, }, //Based on es6 generator syntax effects: { *setNameAsync({ payload }, { put, call }) { yield put({ type: "setName", data: { name: "Superman strong", }, }); }, *testCnode({ payLoad }, { put, call }) { let rel = yield call(apis.testCnode); if (rel.data) { yield put({ type: "setCnodeDataList", data: rel.data.data }); } console.log(rel); }, }, subscriptions: { haha({ dispatch, history }) { history.listen(({ pathname }) => { if (pathname === "/user") { dispatch({ type: "testPath", }); } }); }, }, };
routes/IndexPage.jsx
import React from "react"; import { connect } from "dva"; import * as apis from "../services/example"; class IndexPage extends React.Component { handleSetName = () => { this.props.dispatch({ type: "indexTest/setName", data: { name: "Pig man", }, }); }; handleSetNameAsync = () => { this.props.dispatch({ type: "indexTest/setNameAsync", data: { name: "Pig man", }, }); }; testCnode = () => { this.props.dispatch({ type: "indexTest/testCnode", }); }; componentDidMount() { // apis.testCnode().then((res) => { // console.log(res); // }); apis.mockdata().then((res) => { console.log(res); }); } render() { return ( <div> I'm the home page {this.props.msg} <div>{this.props.name}</div> <button onClick={this.handleSetName}>setName</button> <button onClick={this.handleSetNameAsync}>setNameAsync</button> <button onClick={this.testCnode}>testCnode</button> </div> ); } } const mapStateToProps = (state) => { return { msg: "I Love Beijing Tiananmen ", name: state.indexTest.name, cnodeData: state.indexTest.cnodeData, }; }; export default connect(mapStateToProps)(IndexPage);
routes/userPage.jsx
import React, { Component, Fragment } from "react"; import { Link } from "react-router-dom"; import Child from "../components/child"; class userPage extends Component { handleToIndex = () => { this.props.history.push("/"); }; render() { return ( <Fragment> <div>I am a user page</div> <Link to="/">home page</Link> <button onClick={this.handleToIndex}>home page</button> <Child /> </Fragment> ); } } export default userPage;
services/examples.js
import request from "../utils/request"; const pox = "/apis/"; export function query() { return request("/api/users"); } export function testCnode() { return request(pox + "/api/v1/topics"); } //Register mock interface export function mockdata() { return request("/api/mockdta"); }
index.js
import dva from "dva"; import "./index.css"; // 1. Initialize const app = dva(); // 2. Plugins // app.use({}); // 3. Model app.model(require("./models/indexTest").default); // 4. Router app.router(require("./router").default); // 5. Start app.start("#root");
router.js
import React from "react"; import { Router, Route, Switch } from "dva/router"; import IndexPage from "./routes/IndexPage"; import userPage from "./routes/userPage"; function RouterConfig({ history }) { return ( <Router history={history}> <Switch> <Route path="/" exact component={IndexPage} /> <Route path="/user" exact component={userPage} /> </Switch> </Router> ); } export default RouterConfig;
.roadhogrc.mock.js
export default { ...require("./mock/testMock"), };
.webpackrc
{ "proxy":{ "/apis":{ "target":"https://cnodejs.org", "changeOrigin":true, "pathRewrite":{"^/apis":""} } } }