DvaJs:React数据流解决方案
摘要
本文最终想要达到的目的是掌握DvaJs的使用,从起源开始讲起。文章内容借鉴了阮一峰博客与各技术官网。
Flux篇
Fulx是什么?
Flux 是一种架构思想,专门解决软件的结构问题。它跟MVC架构是同一类东西,但是更加简单和清晰。
为什么要有Flux架构?因为15年Facebook的React火了,但是React有它的缺点,React本身只涉及UI层,搭建大型的前端项目时,只用React就不够用了,必须要有前端框架。不然会出现代码杂乱无法管理,代码重复等问题。
所以Facebook提出了Flux架构,用来管理前端项目结构与项目数据流动。它的具体作用还要看他的实现框架(Flux有很多实现方式,Redux就是其中一种)。
Flux的基本概念
Flux将一个前端应用分为四个部分:
- View: 视图层
- Action(动作):视图层发出的消息(比如mouseClick)
- Dispatcher(派发器):用来接收Actions、执行回调函数
- Store(数据层):用来存放应用的状态,一旦发生变动,就提醒Views要更新页面
Flux 的最大特点,就是数据的"单向流动"
- 用户访问 View
- View 发出用户的 Action
- Dispatcher 收到 Action,要求 Store 进行相应的更新
- Store 更新后,发出一个"change"事件
- View 收到"change"事件后,更新页面
任何相邻的部分都不会发生数据的"双向流动"。这保证了流程的清晰
如图:
详情见阮一峰博客:http://www.ruanyifeng.com/blog/2016/01/flux.html
Generator
dva基于redux和redux-saga,redux不能解决异步回调的问题,所以出现了redux-sage来解决异步回调,而redux-sage是基于Generator的,所以了解dva之前还需要了解ES6推出的Generator。
介绍
Generator是一种函数,有两个区分它和普通函数的地方
- function关键字后面,函数名之前,有个*
- 函数内部有关键字yield
function* hello(){
console.log("first time");
yield '1';
console.log("second time");
yield '2';
console.log("third time");
yield '3';
}
直接调用Generator函数,不会被执行,我们需要这样去调用。
let h=hello();
h.next(); //输出‘first time’ {value: "1", done: false}
h.next(); //输出‘second time’ {value: "2", done: false}
h.next(); //输出‘third time’ {value: "3", done: true}
h.next(); //输出{value: undefined, done: true}
Generator每次执行到yield关键字后就会停下,yield相当于函数的return关键字,返回一个IteratorResult对象,该对象包含两个属性{value:string,done:boolean},value就是yield后面的值,done表示函数后面是否还有yield
DvaJs
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。 --dvajs.com
基本介绍
dva是蚂蚁金服团队开发的框架,封装了redux、react等,并没有引入任何新概念。
dva的一些概念
- state:model的状态数据
- action:修改state的动作
- dispatch:发送action的函数,将action发送给state
- reducer:同步函数放在这里,同步函数可以直接修改state
- effect:异步函数放在这里,异步函数不能直接修改state
- Subscription:监听路由,路由发生变化时,可以做一些事情
- request:请求后端的函数
主要调用流程
页面=>model=>service
- 页面中使用dispatch调用model,一般是调用model中的effects,也有可能调reducer
- model中包含reducer、effects、subscription。一般的调用顺序是effect调用service中的函数,获取到数据,然后调用reducer中的函数,把获取的数据传给reducer,由reducer去修改state.
- reducer因为要直接修改state,所以它一定要返回数据
- 页面与model没有直接的关联,要在connect中连接起来
页面中使用dispatch
/**
* 连接model与组件后,将model中的数据传给组件
* 组件中可以使用this.props获取
* @param state reduce中的函数返回的state
* @returns 返回一个对象,该对象中的值,可以直接在上面的组件中使用。(this.props)
*/
const mapStateToProps = (state) => {
console.log(state);
return {
...state,
}
}
//这种连接方式是redux的连接方式,好像可以使用注解的方式,待了解。
export default connect(mapStateToProps)(Index);
model中定义reducer effect
import { Effect, ImmerReducer, Reducer, Subscription } from 'umi';
import { getRemoteList} from './service';
const IndexModel: IndexModelType = {
namespace: 'users',
state: {
tableData: [],
result:{}
},
effects: {
//effects中的函数的参数为(action:{payload},effects:{put,call}),有时会简写为({payload},{put,call})
*getTest(action, effects) {
const serviceData=yield effects.call(getRemoteList,{currentPage:1,pageSize:5})
yield effects.put({
type:'get',
payload:{
tableData:data,
result:serviceData,
},
})
},
},
reducers: {
//state是当前状态数据,action包含两个属性{type,payload},type几乎不用,所有有时会写为get(state,{payload})
get(state, {payload}) {
let _state={...state};
// _state=action.payload;
//返回的数据比较讲究,必须包含state中的数据,如果不包含所有,也可以,只是ts会报错
return {
...payload,
};
},
// 启用 immer 之后
// save(state, action) {
// state.name = action.payload;
// },
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname }) => {
if (pathname === '/') {
dispatch({
type: 'query',
});
}
});
},
},
};
export default IndexModel;
service中定义request,request请求后端
umi内部提供两种方式请求后端umi-request和request
这里使用request(因为umi-request更高深) request文档见这里
请求后端都是异步函数,定义异步函数的两种方式:
- async function function_name(){}
- const name = async ()=>{}
import request, { extend } from 'umi-request';
import { message } from 'antd';
import BASE_API from '@/myConfig/base_api';
export const getRemoteList = async ({
currentPage,
pageSize,
}) => {
return request(
`${BASE_API}/checkInfo/6/0/${pageSize}/${pageSize}`,
{
method: 'get',
},
)
.then(function(response) {
return response;
})
.catch(function(error) {
return false;
});
};
这是基本的reuest,下面是加了异常处理的request,再把这个异常处理重构出去,就成了统一异常管理
import request, { extend } from 'umi-request';
import { message } from 'antd';
import BASE_API from '@/myConfig/base_api';
const errorHandler = function(error: any) {
if (error.response) {
if (error.response.status > 400) {
message.error(error.data.message ? error.data.message : error.data);
}
} else {
//请求已经发送但是没有回应
message.error('网络不佳,请稍后再试!');
}
throw error; // If throw. The error will continue to be thrown.这里可以看一下
// return {some: 'data'}; If return, return the value as a return. If you don't write it is equivalent to return undefined, you can judge whether the response has a value when processing the result.
// return {some: 'data'};
};
const extendRequest = extend({ errorHandler });
export const getRemoteList = async ({
currentPage,
pageSize,
}: {
currentPage: number;
pageSize: number;
}) => {
return extendRequest(
`${BASE_API}/checkInfo/6/0/${pageSize}/${pageSize}`,
{
method: 'get',
},
)
.then(function(response) {
console.log("成功");
return response;
})
.catch(function(error) {
console.log("失败");
return false;
});
};
其他
tsx、jsx、ts文件的区别
- 使用了TypeScript的js文件就叫ts文件
- 使用了Jsx语法的js文件就叫jsx文件
- 使用了TypeScript和Jsx语法的js文件就叫tsx文件
函数参数
dva中很多函数的参数都是一个对象,对象中有多个属性,所以有多种写法:
function(A,B) //B对象中包含c属性和d属性
function(A,{c,d})
function(A,{d})