Data flow scheme of transforming old project with dva

Keywords: Javascript React TypeScript axios less

Preface

Recently, I had some trouble transferring my scaffolding project to TypeScript.

Before the project, the system of react + react Redux + Redux thunk + Redux actions + Redux promise was adopted.

When a project is converted to TypeScript, react and react Redux are perfect conversions.

Redux actions transformation is also initially completed, but various declarations to adapt to TypeScript are strange, and some type inference errors.

Baidu once replaced it with typesafe actions, and then combined with Redux thunk and Redux promise, various TypeScript types were wrong.

These types of inference are weird. There are no relevant articles on the technical system on the Internet. After debugging, the heart gradually collapses.

Therefore, the next step is to replace the data flow scheme of react Redux + Redux thunk + Redux actions + Redux promise with dva.

About dva

Let's not talk about the introduction of dva. Here's a link: dva Guide.

It is enough to understand that it is based on the encapsulation of redux + react router + redux saga.

My previous scaffold was to divide action, reducer+initialState into different files for processing.

dva, on the other hand, puts reducer s, initialstates, action s, saga and other data streams together in the model.

Install dva

On the Internet, many are installed with DVA cli, and then use DVA QuickStart.

However, we are an old project. We only want to use the data flow scheme of dva, so it is impossible to do so. Just install the dva:

    npm i --save dva

When the blog was published, the latest stable version of dva was: 2.4.1.

Take a look at the code before transformation

Before the transformation, let's look at the code of the original entry js:

import React, { Suspense } from 'react';
import { createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import promiseMiddleware from 'redux-promise';
import ReactDOM from 'react-dom';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import reducer from 'store/reducers';
import './app.less';

import Frame from 'modules/layout/Frame'

function Loading() {
  return <div>Loading...</div>;
}
const PageMain = React.lazy(() => import('modules/pageMain'));

const store = createStore(reducer, applyMiddleware(thunk, createLogger, promiseMiddleware));

const App = () => (
  <Provider store={store}>
    <Router>
      <Suspense fallback={<Loading />}>
        <Frame>
          <Switch>
            <Route path='/' component={PageMain} />
          </Switch>
        </Frame>
      </Suspense>
    </Router>
  </Provider >
);

ReactDOM.render(<App />, document.getElementById('app'));

Transformation entrance js

Paste the modified code first:

import createLogger from 'redux-logger';
import dva from 'dva'

import routerConfig from './route/index'
import pageMainModel from 'modules/pageMain/model'
import { createHashHistory } from 'history';

const app = dva({
  history: createHashHistory(),
  onAction: createLogger
});

app.model(pageMainModel)

app.router(routerConfig)

app.start('#app')

First, we create a new dva object, which sets the route as hash route, and integrates the redux logger, the redux middleware.

More parameter configurations can be viewed: API document.

Then we call

app.model(pageMainModel)

This step mainly sets the initialization of data in the data flow and how to process it, which will be discussed later.

Next call

app.router(routerConfig)

In this step, the dva sets the route of the application. This is because the react router DOM is integrated in the dva, so the route can be set.

Of course, next

app.start('#app')

It is also well understood, corresponding to the ReactDOM.render in the original code.

Route modification

The above code looks a lot simpler than the original code. Most of the reason is that I didn't extract the route configuration.

So let's talk about route/index after transformation:

import React, { Suspense } from 'react'
import { Router, Switch, Route } from 'dva/router'
import Frame from 'modules/layout/Frame'

function Loading() {
  return <div>Loading...</div>;
}

const PageMain = React.lazy(() => import('modules/pageMain'));

const RouterConfig = (({ history }) => (
  <Router history={history}>
    <Suspense fallback={<Loading />}>
      <Frame>
        <Switch>
          <Route path="/" >
            <PageMain />
          </Route>
        </Switch>
      </Frame>
    </Suspense>
  </Router>
));

export default RouterConfig;

Because of the integration of react router DOM, there is basically no need to change, just according to the format of dva.router.

What should be noted here is that the react router DOM integrated by dva is v4.1.2, which can't recognize the latest lazy loading mode of React.lazy.

Therefore, change the lazy load component PageMain configured on the component parameter of Route component to the sub component of Route component, otherwise an error will be reported.

Transform reducer

First look at our original code:

import { handleActions } from 'redux-actions';
import * as T from './actionTypes';


const initialState = {
  fundDatas: []
};

const pageMainReducer = handleActions({
  [T.GET_DATA_LIST]:
  {
    next(state, action) {
      if (!action.payload.data.Data) {
        return {
          ...state,
          fundDatas: []
        }
      }
      return {
        ...state,
        fundDatas: action.payload.data.Data.LSJZList.reverse().map(l => ({
          netValueDate: l.FSRQ,
          netValue: l.DWJZ,
          totalNetValue: l.LJJZ,
          dayOfGrowth: l.JZZZL
        }))
      }
    },
    throw(state) {
      return state;
    },
  }
}, initialState);

export default pageMainReducer;

Here t.get? Data? List is the text of the type of action.

Then take a look at the modified code

import { getFundData } from 'services/fundService'

export default {
  namespace: 'pageMainModel',
  state: {
    fundDatas: []
  },
  effects: {
    *getDatas({ payload }, { call, put }) {
      const { data } = yield call(getFundData, payload);
      const fundDatas = data.Data.LSJZList.reverse().map((l) => ({
        netValueDate: l.FSRQ,
        netValue: l.DWJZ,
        totalNetValue: l.LJJZ,
        dayOfGrowth: l.JZZZL
      }))
      yield put({ type: 'refreshDatas', payload: fundDatas });
    },
  },
  reducers: {
    refreshDatas(state, { payload }) {
      return {
        ...state,
        fundDatas: payload
      }
    },
  },
};

The integrated dva is Redux saga, so those with experience should understand it.

A namespace is a namespace that serves as a key for each model.

state is the initialization value.

effects are mainly used to handle asynchronous operations.

The reducer part is similar to the original reducer.

In addition, the format of getFundData is as follows:

export const getFundData = (params: IGetFundParams) => {
  const { fundCode, startDate, endDate, pageSize } = params
  return axios.get(`http://localhost:8011/getList?fundCode=${fundCode}&startDate=${startDate}&endDate=${endDate}&pageSize=${pageSize}`)
};

Transformation of action

Here is the original action

import { createAction } from 'redux-actions';
import axios from 'axios'
import * as T from './actionTypes';

/**
* Access to fund data
*/
export const getDataList = createAction(T.GET_DATA_LIST, (fundCode, startDate, endDate, pageSize) => {
  return axios.get(`http://localhost:8011/getList?fundCode=${fundCode}&startDate=${startDate}&endDate=${endDate}&pageSize=${pageSize}`)
});

The call mode after connect to the corresponding component is:

this.props.getDataList(fundCode, startDate, endDate, pageSize)

After the previous transformation, we no longer need the action file. When using it, we directly use the following methods:

this.props.dispatch({
  type: 'pageMainModel/getDatas',
  payload: { fundCode, startDate, endDate, pageSize }
})

In this case, the dispatch is the direct encapsulation of the connect ion of the dva and passed to the component.

Modification of vessel components

Give the modified code first:

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

const mapStateToProps = ({ pageMainModel }) => {
  return {
    fundDatas: pageMainModel.fundDatas
  };
}

/**
* home page
*/
@connect(mapStateToProps)
export default class PageMain extends React.Component {
  componentDidMount() {
    this.getList()
  }
  // Access to fund data
  getList = () => {

    // ...

    this.props.dispatch({
      type: 'pageMainModel/getDatas',
      payload: { fundCode, startDate, endDate, pageSize }
    })
  }

  render() {
    //...
  }
}

Before the transformation, the code will not be given, because there are few changes involved. Just pay attention to the consistency of the dva, just transfer the state of redux to the component, and do not need to transfer the function calling the action.

summary

The purpose of the initial transformation is to ts, but when we go to see the relevant examples of the dva after the transformation, we find the project examples recommended by the official website of the dva umi-dva-antd-mobile model files in the

// @ts-ignore

To hide the ts error in the file. (it's a bit embarrassing, but the whole process of transformation is quite fruitful.)

My own solution is not to change the suffix of model.js to ts, and the project will avoid the transformation and detection of JS files.

Generally speaking, I just made a transformation of my own scaffolding project. If the old project is large, the transformation is still very laborious.

The code in the blog has been more or less deleted. For the specific project code, see my Github project: Scaffold project .

Posted by TonyB on Fri, 13 Dec 2019 04:46:57 -0800