使用dva改造React旧项目的数据流方案
前言
最近在给自己的脚手架项目转到typescript时,遇到了一些麻烦。
项目之前采用的是react + react-redux + redux-thunk + redux-actions +redux-promise的体系。
当项目转typescript时,react和react-redux这种完美转换。
redux-actions转换也初步完成,但是各种为了适应typescript的声明很奇怪, 并且有些类型推断错误。
百度了一下,用typesafe-actions去替换,然后与redux-thunk 和redux-promise结合后,各种typescript类型错误。
这些类型推断稀奇古怪,网上也没有这个技术体系的相关文章,下班后调试代码,内心逐渐崩溃。
于是有了接下来的举措,用dva去替换react-redux + redux-thunk + redux-actions +redux-promise的数据流方案。
关于dva
关于dva的介绍咱们就不说了,这里给个链接:。
明白它是基于 redux + react-router + redux-saga 的封装就够了。
我之前的脚手架是将action,reducer+initialstate 分成了不同的文件来处理。
而dva将 reducer, initialstate, action, saga 这些数据流相关的都放在model中一起处理。
安装dva
网上很多都是安装dva-cli,然后再用 dva-quickstart。
然而我们是旧项目,只想用dva的数据流方案,所以不可能这么做,只需要安装dva就好了:
npm i --save dva
博客发布时dva最新稳定版是:2.4.1。
先看一下改造前的代码
在改造之前我们看一下原有入口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'));
改造入口js
先贴上改造后的代码:
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')
首先我们新建了一个dva对象,这个dva对象设置了路由为hash路由,并且集成了redux-logger这个redux中间件。
更多参数配置可以查看:api文档。
然后我们调用
app.model(pagemainmodel)
这一步主要是设置了数据流中数据的初始化和如何进行处理,具体后面会讲。
接着调用
app.router(routerconfig)
这一步dva设置了应用的路由,这是因为dva中集成了react-router-dom,所以可以进行路由设置。
当然接下来
app.start('#app')
也很好理解,对应原有代码中的 reactdom.render 。
改造路由部分
上面的代码比原有代码看起来精简很多,有一大半原因是我没有将route的配置提取出去。
所以我们先讲一下改造后路由 /route/index :
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;
因为集成的是react-router-dom,所以基本上不需要改变,只需要按照dva.router的格式修改即可。
这里需要注意的是dva集成的react-router-dom是v4.1.2版本,还不能识别这种react.lazy这种最新的懒加载方式。
所以要将配置在route组件的component参数上的懒加载组件pagemain,修改为route组件的子组件,否则会报错。
改造reducer
首先看下咱们原有的代码:
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;
这里t.get_data_list是action的type的文本。
然后看下改造后的代码
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 } }, }, };
dva集成的是redux-saga,所以有使用经验的应该比较懂。
namespace是命名空间,用作各个model的key。
state是初始化值。
effects主要用来处理异步操作。
reducer部分和原有reducer类似。
另外getfunddata的格式如下:
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}`) };
改造action
下面是原有action
import { createaction } from 'redux-actions'; import axios from 'axios' import * as t from './actiontypes'; /** * 获取基金数据 */ 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}`) });
connect到相应组件后的调用方式为:
this.props.getdatalist(fundcode, startdate, enddate, pagesize)
然后我们经过之前的改造已经没有了不再需要action这个文件,使用的时候直接使用以下方式:
this.props.dispatch({ type: 'pagemainmodel/getdatas', payload: { fundcode, startdate, enddate, pagesize } })
这里的dispacth是dva的connect直接封装传递到组件的。
改造容器组件
先给改造后的代码:
import react from 'react'; import { connect } from 'dva'; const mapstatetoprops = ({ pagemainmodel }) => { return { funddatas: pagemainmodel.funddatas }; } /** * 首页 */ @connect(mapstatetoprops) export default class pagemain extends react.component { componentdidmount() { this.getlist() } // 获取基金数据 getlist = () => { // ... this.props.dispatch({ type: 'pagemainmodel/getdatas', payload: { fundcode, startdate, enddate, pagesize } }) } render() { //... } }
改造前的代码就不给了,因为涉及的改动比较少,只需要注意dva的conenct只需要将redux的state传到组件,不需要将调用action的函数传入即可。
总结
最开始改造的目的是为了ts,但是等改造完成再去看dva的相关示例时,发现dva官网推荐的项目示例中的model文件中都加了
// @ts-ignore
来隐藏文件中的ts报错。(感觉有点囧啊,不过整个改造的过程还是蛮多收获的)
我自己的方案是将model.js的后缀不修改为ts,项目会避开对js文件的转换和检测。
总的来说,我只是对自己做的脚手架项目进行了一个改造,如果旧项目较大的话,改造起来还是挺费劲的。
博客中的代码都或多或少进行了删减,具体的项目代码可以查看我的github项目:脚手架项目。