前端开发中的数据管理(Taro+Dva)
Dva没有提出新的框架,是在原本的React+Redux基础上进行封装,简化了开发流程,解决了直接使用 Redux 时的 “配置繁琐、样板代码多、工具链整合复杂” 等问题。Redux要手动配置storeactionreducer,并且若要进行异步请求,要借助中间件redux-saga,代码分散在多个文件中(如actions.jssagas.js而Dva引入了Model的概念,将数据state、同步
问题背景
在进行前端开发时,除了完成UI界面之外,还要关注代码逻辑,更要关注数据流。数据是页面的灵魂,因此数据管理就变得很重要了。
页面动态化主要是依靠state中的数据动态变化,但当页面变得庞大的时候,state的变化往往牵一发动全身,因此需要纯净的UI组件,除了渲染逻辑,不杂糅其他(如网络请求),所有组件降级为无状态组件,仅仅依靠props渲染,专注UI渲染
(注:这里说的组件主要是指 page 下面的页面组件,对于 component 下的组件本身就应该是比较通用的组件,更应该仅仅依赖 props 渲染,它们也不应该有 model,数据应该通过在页面组件中通过 props 传递过去)。
本篇博客分享一下React项目中的Dvajs实践。
Dva概述
Dva没有提出新的框架,是在原本的React+Redux基础上进行封装,简化了开发流程,解决了直接使用 Redux 时的 “配置繁琐、样板代码多、工具链整合复杂” 等问题。
Redux要手动配置store、action、reducer,并且若要进行异步请求,要借助中间件redux-thunk、redux-saga,代码分散在多个文件中(如 actions.js、reducers.js、sagas.js)。
而Dva引入了Model的概念,将数据state、同步操作reducer,异步操作effects、订阅subscriptions,集中在一个model文件中,简化了模板代码。
贴上一段官方的model模板代码
import { queryUsers, queryUser } from '../../services/user';
export default {
state: {
user: {},
},
effects: {
*queryUser({ payload }, { call, put }) {
const { data } = yield call(queryUser, payload);
yield put({ type: 'queryUserSuccess', payload: data });
},
},
reducers: {
queryUserSuccess(state, { payload }) {
return {
...state,
user: payload,
};
},
},
test(state) {
console.log('test');
return state;
},
};
- Model 中的
state对应 Redux 的store状态; reducers对应 Redux 的reducer(纯函数,用于同步更新状态);- dva 内部会自动创建 Redux 的
store,并将所有 Model 的reducers合并为根 reducer。 - Redux 本身仅支持同步状态更新,异步操作(如接口请求)需要依赖中间件。dva 内置了
redux-saga(处理复杂异步流程的中间件),并通过 Model 中的effects简化了 saga 的使用。
项目实践
项目用的是Taro框架,与umi不同的是,Taro并不内置dva支持,因此需要自己对dva进行封装。
基础依赖配置(package.json)
dva是基于redux的,因此也需要配置redux
{
"dva-core": "*",
"dva-loading": "^3.0.22",
"react-redux": "^7.2.0",
"redux": "^4.0.0"
}
Dva封装
引入工具 → 创建实例 → 配置插件 → 注册模型 → 启动应用 → 暴露接口
import { create } from 'dva-core'; //dva的核心组件
import createLoading from 'dva-loading'; // 用于监听和管理effects的异步操作
function createApp(opt){
app = create(opt);
app.use(createLoading({})); // ✅ 配置 loading 插件
if(!global.registered){ //每次只注册一次,防止重复注册导致的状态混乱
opt.models.forEach(model => app.model(model)); // ✅ 注册所有 models
}
global.registered = true;
app.start(); // 启动dva应用
store = app._store;
// 将store、dispatch挂载到app上,减少对底层的依赖,符合“封装底层细节”
app.getStore = () => store;
dispatch = store.dispatch;
app.dispatch = dispatch;
return app;
}
这有个小细节:项目是热重载的,但由于在创建实例注册模型的时候加了一个判断,因此若在开发过程中添加了model,则需要重新运行项目,否则model不会生效。
应用入口配置
要在Taro中配置dva,肯定需要在应用程序初始化的时候进行挂载,因此要在src/app.js入口文件中进行配置。
import { Component } from 'react'
import { Provider } from 'react-redux'
import 'taro-ui/dist/style/index.scss'
import models from './models'
import dva from './utils/dva';
import './app.less'
import Taro from "@tarojs/taro";
const dvaApp = dva.createApp({
onError(err) {
// 在这里进行错误处理
console.log(err);
},
initialState: {},
models
});
const store = dvaApp.getStore();
class App extends Component {
componentDidMount() {
...
}
componentDidShow() { }
componentDidHide() { }
componentDidCatchError() { }
render() {
return (
<Provider store={store}>
{/* 你的页面组件 */}
</Provider>
)
}
}
export default App
展示一下models文件的内容:将项目中的所有model集中在这个文件中进行记录和管理,没有在这个文件中引用的model不会生效
import globalModel from "./global";
export default [
globalModel,
];
至此前期配置已全部完成,下面给出项目中的使用示例
使用示例
组件与model连接
import {connect} from "react-redux";
// 单个 model 连接
@connect(({VerificationModel})=>({
VerificationModel
}))
// 多个 model 连接
@connect(({WSAWmsWorkModel, WSAWorkOrderModel, WSAStatOfWorkerModel}) => ({
WSAWmsWorkModel,
WSAWorkOrderModel,
WSAStatOfWorkerModel,
}))
组件中使用dispatch
this.props.dispatch({
type: 'WSA_WmsWorkerOrderPoolModel/getPoolList',
})
this.props.dispatch({
type: 'VerificationModel/save',
payload: { username: 'newName' }
})
model中的异步数据流
// 1. 组件触发 action
this.props.dispatch({
type: 'WSA_WmsWorkerOrderPoolModel/getPoolList',
})
// 2. Effect 处理异步请求
*getPoolList({payload},{call, put}){
const response = yield call(PoolService.getWSAPoolList, payload);
if(code===RESPONSE_OBJ.SUCCESS){
yield put({
type: 'save',
payload: { poolList: data }
})
}
}
// 3. Reducer 更新 state
save(state, {payload}) {
return {
...state,
...payload
}
}
// 4. 组件自动重新渲染
跨model通信
在model的方法中调用其他方法通过put进行,若调用的是当前model的方法function1,则type:function1,若调用的是modelA的方法function2,则type:modelA/function1
// 在 VerificationModel 中调用其他 model
yield put({
type: 'dispatchContainerModel/changeRole',
payload: {
roleName: 'worker',
pagePath: 'pages/WSA_Index/index',
}
})
知识点Tips
关于effect和reducer
我们可以注意到,在model中有两类函数,一种是effect,一种是reducer;
effect主要处理复杂逻辑以及异步操作(网络请求等),在执行逻辑上面,调用effect函数会调用redux-saga中间件进行异步操作处理,最终通过put来调用reducer中的函数进行state更新。而reducer则直接调用函数。
在参数方面也可以观察到一些不同,在这里就不展开了,感兴趣的朋友可以查阅官网资料。
项目结构
只展示了本文中出现的文件的目录位置
.
├── app.js
├── src
│ ├── utils // 工具函数目录,存放一些公用的函数或工具类
│ │ ├── dva.js
│ ├── models
│ │ ├── global.js // 页面的全局数据存放在这
│ │ └── index.js //用来记录model,若没在里面注册,则不会生效,原因可见dva.js的代码
│ │ └── xxxmodel.js // 某页面对应的model
│ ├── pages
│ │ ├── index.less
│ │ └── index.js
│ ├── services // 封装网络请求
│ │ └── api.js
├── node_modules
│ └── .cache
│ ├── bundler-webpack
│ ├── mfsu
│ └── mfsu-deps
数据获取
Model中数据的更新也会引起页面的更新,那么如果我想在component中获取数据,或者在Model中获取数据,该如何写呢?
// 获取本model的数据
// src/models/WSA_WmsWorkOrderModel.js
*getWorkOrderList({payload,callback},{call, put, select}){
// 获取本 model 的 state
const screenTime = yield select(state => state.WSAWmsWorkOrderModel.screenTime);
console.log(screenTime)
// ...
}
// 获取其他 model 的 state
*getUserData({payload}, {call, put, select}){
// 获取 VerificationModel 的 state
const userInfo = yield select(state => state.VerificationModel.userId);
// 获取 globalModel 的 state
const selectedIndex = yield select(state => state.globalModel.selectedIndex);
// 获取多个 model 的 state
const { VerificationModel, globalModel } = yield select(state => ({
VerificationModel: state.VerificationModel,
globalModel: state.globalModel
}));
}
// 在组件中获取数据
class WSA_Workbench extends Component {
render() {
// 通过 this.props 访问连接的 model state
const { WSAWmsWorkModel, WSAWorkOrderModel } = this.props;
// 解构获取具体的数据
const { workOrder } = WSAWmsWorkModel;
const { statusCountArray } = WSAWorkOrderModel;
return (
<View>
{/* 使用 state 数据 */}
<Text>{workOrder.length} 个工单</Text>
</View>
);
}
}
参考网址
更多推荐
所有评论(0)