一,什么是Dva
?
1.react
专注于ui
渲染,功能较为单一,在实际开发一个react
项目需要更多库的支持,例如在react
项目中最常用的数据状态管理器redux
和redux-saga
,路由控制react-router
等等。
2.Dva
是轻量级的 React
应用框架,将Redux
,React-Router
,Redux-saga
三个 React
工具库整合在一起,简化了api
,让开发更便捷。
3.我们可以简单的理解,Dva = react-router + rudex + redux-saga
,没有更多的功能,但是有更简洁的api
。
4.官方文档
二,创建一个Dva
项目
1.官方提供了脚手架dva-cli
,用于创建一个Dva
项目
2.首先使用包管理工具npm
安装脚手架: npm install dva-cli -g
3.之后使用 dva new [projectName]
命令来创建一个dva
项目
三,Dva
项目结构
1.使用dva-cli
创建的项目默认的项目结构如下图所示
四,Dva
入口文件
1.在Dva
项目的入口文件,完成了
(1)路由的注册,
(2)model
的注册,
(3)插件的注册,
(4)项目启动,
2.入口文件几乎涉及了Dva
的所有api
import dva from 'dva'; //dva库默认export一个dva函数,用于创建dva实例
import './index.less';
import createLoading from 'dva-loading';
// 1. 初始化
const app = dva();
// 2. 插件
app.use(createLoading()); //use方法用于使用插件,和注册hooks
// 3. 注册model
app.model(require('./models/example').default); //model`方法接受一个model对象,用于注册model
app.model(require('./models/others').default);
...
// 4. 注册路由
app.router(require('./router').default); //router方法接受一个具有固定格式的函数,用于注册路由表
// 5. 项目启动
app.start('#root');
五,定义路由
1.在Dva
项目里,默认的我们在src
文件夹下的router.js
文件定义路由。
import React from 'react';
import { Router, Route, Switch } from 'dva/router'; //路由相关的组件在dva/router中
import IndexPage from './routes/IndexPage';
function RouterConfig({ history }) {
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={IndexPage} />
</Switch>
</Router>
);
}
export default RouterConfig;
2.我们可以使用dva/dynamic
来做代码分割,动态注册model
,提高首屏加载速度
import React from 'react';
import { Router, Route, Switch } from 'dva/router'; //路由相关的组件在dva/router中
import dynamic from 'dva/dynamic'
const routes =[
{ path:'/',
component: ()=>import('./routes/Example'),
model:()=>[import('./models/example')]
}
]
import IndexPage from './routes/IndexPage';
function RouterConfig({ history,app }) {
return (
<Router history={history}>
<Switch>
{routes.map(({path,...dynamic})=><Route path={path} exact component{dynamic({app,...dynamic})} />)}
</Switch>
</Router>
);
}
export default RouterConfig;
六,定义model
1.在Dva
项目中,我们在models
文件夹下定义model
2.一个基本的model
如下
export default {
namespace: 'example', //model的命名
state: {
text:'hello dva', //定义数据的地方,数据的改变是响应式的
},
subscriptions: { //订阅数据源,相当于一个监听器,可以监听路由变化,鼠标,键盘变化,服务器连接变化,状态变化等
setup({ dispatch, history }) { //监听路由变化实例
history.listen(({ pathname }) => {
if (pathname === '/xxx') {
//do something
}
});
},
},
effects: {
*fetch({ payload }, { call, put, select,all }) { //effects里的函数是es6中的generator函数, 用于定义异步操作
yield put({ type: 'save' }); //put类似于dispatch,用于触发action
yield call(service,params) //call可用于请求服务端
yield select(state=>state.example>) //select用于获取model里的数据,要在effects函数中使用state数据,必须使用select**。
yield all([]) //类似于Promise的Promise.all([])
},
},
reducers: {
save(state, action) { //reducers函数的第一个参数是该model的state,action是触发的动作,包含了传递了参数
return { ...state, ...action.payload }; //reducers函数返回的值作为最新的state
},
updateState(state, {payload}){ //通常我们只需要action里的负荷载体,因此可直接解构出来
return { ...state, ...action.payload };
},
},
};
3.可以看出 model
是一个对象,包含了5
个选项,其中除了namespace
,state
是必须定义的,其他的是可选项
(1)namespace
: 接收一个字符串表示model
的命名空间,同时也是他在全局 state
上的属性
(2)state
: 接收一个对象表示状态初始值,优先级低于传给 dva()
的 opts.initialState
。
(3)subscriptions
: 订阅数据源
(4)effects
: 接收一个对象定义异步操作,如请求服务端服务
(5)reducers
: 接收一个对象定义同步操作,唯一可以 修改 state
的地方,
七,将model
和UI
组件关联
1.在定义了model
,并且注册了model
(在入口文件,或者注册路由时动态引入),最后一步还需将model
和UI
组件关联起来。
2.dva
中export
了一个高阶组件connect
用于将model
和UI
组件关联。
import { connect } from 'dva';
3.connect
是一个函数,接受两个函数作为参数,并且返回一个函数,返回的函数接受一个组件作为参数,返回连接了state
的包装后的组件
import React from 'react';
import { connect } from 'dva';
function Example(props){
return <div></div>
}
const mapStateToProps =state=>({example:state.example})
export default connect(mapStateToProps,mapDispatchToProps)(Example);
//或者
export default connect(({example})=>({example}))(Example);
4.第一个函数mapStateToProps
用于建立state
和组件props
的对应关系,其中参数state
包含了所有全局注册的model
和注册路由时动态导入的model
5.被connect
之后的组件props
返回值如下
(1)包含了dispatch
用于触发action
(2)连接了model
的state
,如下的example
八,完整数据流
1.数据的改变发生通常是通过用户交互行为或者浏览器行为触发的。当此类行为会改变数据的时候可以通过 dispatch
发起一个 action
,如果是同步行为直接通过 Reducers
改变 State
,如果是异步行为(副作用)会先触发 Effects
然后流向 Reducers
最终改变 State
。
2.从上图不难发现 dispatch
是用于发起 数据改变的唯一方法,触发源可以在UI
组件中,如某次点击事件需要改变state
,或者触发源可以在subscription
中,例如当我们使用subscription
监听到路由变化时,初始化state
。
3.dispatch
接受一个action
, action
描述一个行为,包含type
和payload
(1)type
是指定的动作,即model
中定义的reducers
和effects
(2)payload
是载体,用于承载参数
let action = { type:'example/save', payload:{ text:'hello dva again'}}
<div onClick={()=>dispatch(action)}></div>
4.当用dispatch
触发一个副作用effects
时,返回的是一个Promise
九,loading
对象,
1.dva
框架内置了loading
插件,我们可以在全局中import
后使用。
import createLoading from 'dva-loading';
const app = dva(createLoading());
这样在所有的组件中都可以使用loading
对象。
2.具体用法,用于异步操作,在异步操作开始时为true
,结束时为false
。
export default connect(
({
userAndlogin,
loading,
}) => ({
userAndlogin,
submitting: loading.effects['userAndlogin/login'],
}),
)(Login);