什么是dva
dva 是一个基于 redux 和 redux-saga 的数据流方案.
为什么使用dva
因为它简化了react引入redux的过程。
传统redux与dva对比
redux:
开发时,我们需要action,reducer等文件,并且需要自行分类,不太清晰。
dva:
开发时,把 store 及 saga 统一为一个 model 的概念, 写在一个 js 文件里面,分类清晰。
增加了一个 Subscriptions, 用于收集其他来源的 action。
如何使用dva
函数式组件与非函数式组件
概要
HelloWorldModel.js
export default {
namespace: 'HelloWorldModel',
state: {str:"hi"},
reducers: {
},
};
这是react组件的创建:
class HelloWorld extends Component {
constructor() {
super()
this.state = { }
}
sayHi () {
alert('this.props.HelloWorldModel.str)
}
render () {
return (
<div onClick={this.sayHi.bind(this)}>Hello World</div>
)
}
}
export default connect(({ HelloWorldModel }) => ({
HelloWorldModel,
}))(HelloWorld);
函数式组件
const HelloWorld = (props) => {
const sayHi = (event) => alert(this.props.HelloWorldModel.str)
return (
<div onClick={sayHi}>Hello World</div>
)
}
export default connect(({ HelloWorldModel }) => ({
HelloWorldModel,
}))(HelloWorld);
属性与状态
页面的数据分为2大类:
1.页面属性:Modal打开或者关闭,与用户行为相关,与后台数据无关
2.页面状态:Table展示的数据内容,与用户行为无关,与后台数据相关
我通常在react组件中直接使用state作为页面属性,将页面状态存在model中,
而函数式组件,则将页面属性和状态全部存放在model中。
生命周期
上面是react组件和函数式组件的写法区别,
通过react组件写法声明的组件继承了“react.Component”的一些属性及方法,如生命周期钩子函数等。
而函数式组件只是个返回dom节点的方法,所以不具备生命周期函数。
因为dva的数据流是单向的,
用户行为=》视图=》model.Effect=>model.Reducer=>store变化=>视图更新,
react组件可以在“componentWillReceiveProps”钩子中决定是否允许当前组件内的数据流动,
函数式组件只能被动接收数据流动。
页面与状态的绑定和流转
如上述代码,页面与model的绑定,是通过connect方法实现的,在connect方法中,入参可以抓取到所有导入的model文件,我们可以在每个组件中(需要关联model的)“重载”connect方法,只获取我们当前页面所需的model,展示在页面上。而页面的事件则可以使用dispatch触发model的Effect事件与服务端交互之后执行reducer,store(model数据)变化后,页面因为connect了model,相当于订阅了model的变化,数据流入,页面更新。
公共状态抽离
一些公用的状态是可以抽离到同一个model中的,如登录态,上一次查询参数等。
实际应用中的一些解决方案
动态组件
dva提供dynamic方法支持动态加载组件和model,
import dynamic from 'dva/dynamic';
const UserPageComponent = dynamic({
app,
models: () => [
import('./models/users'),
],
component: () => import('./routes/UserPage'),
});
通过动态加载后,build出来的文件将index.js切割成index.js(体积变小) + 1.async.js等等,
但是index.js默认是通过"./"来加载1.async.js的,对于前端静态资源与服务端应用不在同一台服务器的情况,如
java boot应用 发布在http://xxx.com/a =》 a.ftl 中引用 dva build之后的静态资源,而静态资源发布却是在另一台服务器上http://zzz.com/dvaBuild/dist,
index.js还是去访问默认的"./1.async.js" 其实访问的是http://xxx.com/a/1.async.js,...。
可以直接在页面script标签中,
window.__webpack_public_path__ = 'http://zzz.com/dvaBuild/dist';
路由跳转
权限判断(接入第三方登陆)
1.是否登陆
在GlobalModel中保存着登录态,路由中使用AuthComponent替换Route,在渲染路由组件的时候就会判断是否登陆,如果没有登陆渲染正在登陆loading页面,等待第三方登陆接口判断已经登陆,页面会自动更新为路由组件。
这是第三方登陆,如果是内部登陆使用react-router跳转。
AuthComponent
@connect(({GlobalModel}) => GlobalModel)
export default class AuthComponent extends PureComponent {
render() {
const { component: Component, isLogin, ...rest } = this.props;
return (
<Route
{...rest}
render={props =>
isLogin == 1 ? (
<Component {...props}/>
) : (
<Logining></Logining>
)
}
/>
)
}
}
GlobalModel
effects: {
*CheckLogin({payload,callback}, {call,select,put}) {
const loginfoResult = yield call(getLoginInfo);
if(loginfoResult && loginfoResult.ResponseStatus && loginfoResult.ResponseStatus.Ack == "Success" && loginfoResult.userBasicInfoDTO){
if(loginfoResult.userBasicInfoDTO && loginfoResult.userBasicInfoDTO.userAccount && checkPermission(loginfoResult.userBasicInfoDTO.userAccount)){
yield put({type:"save",loginInfo:loginfoResult})
callback();
}else{
yield put( routerRedux.push('/NoPermission') ); //没有权限
}
}else{
window.location.href = getLoginUrl();
}
},
需要在各个路由页面绑定的model的路由切换订阅方法中做登陆判断,
页面切换就要判断是否登陆或鉴权
subscriptions: {
setup({ dispatch, history }) { // eslint-disable-line
return history.listen((location) => {
dispatch({
type: "GlobalModel/onShow",
callback: () => {
dispatch({type:"BaseInfoQuery",payload:{}});
}
})
})
},
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。