dva 是由阿里架构师 sorrycc 带领 team 完成的一套前端框架,在作者的 github 里是这么描述它的:“dva 是 react 和 redux 的最佳实践”。
dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装。dva 是 react 和 redux 的最佳实践。最核心的是提供了 app.model
方法,用于把 reducer, initialState, action, saga 封装到一起。官网 dva = React-Router + Redux + Redux-saga
1.安装 dva-cli
npm install dva-cli -g
2.扎到安装项目的目录
cd ylz_project/my_reactdemo
3.创建项目:Dva-test项目名
dva new Dva-test
4.进入项目
cd Dva-test
5.启动项目
npm start
下面都是官网的知识点
数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。
model 是 dva 中最重要的概念,Model 非 MVC 中的 M,而是领域模型,用于把数据相关的逻辑聚合到一起,几乎所有的数据,逻辑都在这边进行处理分发
State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
// dva()初始化
const app = dva({
initialState: { count: 1 },
});
// modal()定义事件
app.model({
namespace: 'count',
state: 0,
});
//初始值,我们在 dva() 初始化的时候和在 modal 里面的 state 对其两处进行定义,其中 modal 中的优先级低于传给 dva() 的 opts.initialState
Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。表示操作事件,可以是同步,也可以是异步
action 的格式如下,它需要有一个 type ,表示这个 action 要触发什么操作;payload 则表示这个 action 将要传递的数据
我们通过 dispatch 方法来发送一个 action
/*Action
Action 表示操作事件,可以是同步,也可以是异步
{
type: String,
payload: data
}
*/
dispatch(Action);
dispatch({ type: 'todos/add', payload: 'Learn Dva' });
其实我们可以构建一个Action
创建函数,如下
const USER-LIST = 'USER-LIST'//用户列表
//action 函数
function user(data){
return {type:MSG_READ ,payload:data}
}
dispatch(user(data))
dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,常见的形式如:
dispatch({
type: 'user/add', // 如果在 model 外调用,需要添加 namespace
payload: {}, // 需要传递的信息
});
Reducer(也称为 reducing function)函数接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。
//state的值
const initState = {
users:{},//用户信息
}
//1 reducer 函数
export function chat(state=initState, action){
switch(action.type){
case USER_LIST:
return {...state,users:action.payload.users}
default:
return state
}
}
Reducer 的概念来自于是函数式编程,很多语言中都有 reduce API。如在 javascript 中:
[{x:1},{y:2},{z:3}].reduce(function(prev, next){
return Object.assign(prev, next);
})
//return {x:1, y:2, z:3}
在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。需要注意的是 Reducer 必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。
用于处理异步操作和业务逻辑,不直接修改 state,简单的来说,就是获取从服务端获取数据,并且发起一个 action 交给 reducer 的地方。基于 Redux-saga 实现。Effect 指的是副作用。根据函数式编程,计算以外的操作都属于 Effect,典型的就是 I/O 操作、数据库读写。
常见的操作:
1.put
用于触发 action 。
yield put({ type: 'todos/add', payload: 'Learn Dva' });
2.call
用于调用异步逻辑,支持 promise 。
const result = yield call(fetch, '/todos');
3.select
用于从 state 里获取数据。
const todos = yield select(state => state.todos);
简单的理解Redux-Saga
subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、当前页面的url、服务器的 websocket 连接、history 路由变化等等。
一般格式是:
subscriptions: {
setup({ dispatch, history }) {
},
},
异步数据初始化
比如:当用户进入 /users 页面时,触发 action users/fetch 加载用户数据。
app.model({
subscriptions: {
setup({ dispatch, history }) {
history.listen(({ pathname }) => {
if (pathname === '/users') {
dispatch({
type: 'users/fetch',
});
}
});
},
},
});
键盘事件
import key from 'keymaster';
...
app.model({
namespace: 'count',
subscriptions: {
keyEvent({dispatch}) {
key('⌘+up, ctrl+up', () => { dispatch({type:'add'}) });
},
}
});
项目中的 router.js。
这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作。
Route Component 表示 Router 里匹配路径的 Component,通常会绑定 model 的数据
import { connect } from 'dva';
function App() {
return App;
}
function mapStateToProps(state) {
return { todos: state.todos };
}
export default connect(mapStateToProps)(App);
官网.
opts 包含:
history:指定给路由用的 history,默认是 hashHistory 关于react-router中的hashHistory
和browserHistory
的区别大家可以看:react-router。
initialState:指定初始数据,优先级高于 model 中的 state,默认是 {},但是基本上都在modal里面设置相应的state。
如果要配置 history 为 browserHistory,可以这样:
import createHistory from 'history/createBrowserHistory';
const app = dva({
history: createHistory(),
});
另外,出于易用性的考虑,opts
里也可以配所有的 hooks ,下面包含全部的可配属性:
const app = dva({
history,
initialState,
onError,
onAction,
onStateChange,
onReducer,
onEffect,
onHmr,
extraReducers,
extraEnhancers,
});
这里最常见的就是dva-loading插件的配置,就是引入第三方插件的时候使用
import createLoading from 'dva-loading';
...
app.use(createLoading(opts));
model 是 dva 中最重要的概念。 这个是你数据逻辑处理,数据流动的地方。
A.namespace
model 的命名空间,同时也是他在全局 state 上的属性,只能用字符串,不支持通过 . 的方式创建多层命名空间。
B.state
初始值,优先级低于传给 dva() 的 opts.initialState。
const app = dva({
initialState: { count: 1 },
});
app.model({
namespace: 'count',
state: 0,
});
此时,在 app.start() 后 state.count 为 1 。
同上面
C.reducer
D.Effect
E.subscription
总结:
import { Router, Route } from 'dva/router';
app.router(({ history }) => {
return (
);
});
推荐把路由信息抽成一个单独的文件,这样结合 babel-plugin-dva-hmr 可实现路由和组件的热加载。
比如:app.router(require('./router'));
但是如果你的项目特别的庞大,我们就要考虑到相应的性能的问题,就要router
按需加载的写法。
启动应用,项目跑起来
app.start('#root');
A.antd 和 babel-plugin-import 。babel-plugin-import 是用来按需加载 antd 的脚本和样式的,
npm install antd babel-plugin-import --save
B.编辑 .webpackrc,使 babel-plugin-import 插件生效。
{
"extraBabelPlugins": [
["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }]
]
}
npm install antd-mobile --save
直接引用css就好:import 'antd-mobile/dist/antd-mobile.css'
按需加载:
安装babel-plugin-import: cnpm install babel-plugin-import --save
找到根目录下的.webpackrc文件,并在该文件中添加以下代码
{
"extraBabelPlugins": [
["import", { "libraryName": "antd-mobile", "style": "css" }]
]
}
官网:操作 上手快速
一.路由定义:router.js
import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import User from './routes/user';
function RouterConfig({ history }) {
return (
);
}
export default RouterConfig;
浏览器访问:http://localhost:8000/#/user
二.定义 model/user:设置state的值
export default {
namespace: 'user',
state: {
list: [],
total: null,
loading: false, // 控制加载状态
current: null, // 当前分页信息
currentItem: {}, // 当前操作的用户对象
},
}
三.页面组件定义:
1. routes/user
import React from 'react';
import { connect } from 'dva';
import styles from './user.css';
import UserList from '../components/users/userList';
import UserSearch from '../components/users/userSearch';
import UserModal from '../components/users/userModal';
function User() {
const userSearchProps = {};
const userListProps = {};
const userModalProps = {};
return (
{/* 用户筛选搜索框 */}
{/* 用户信息展示列表 */ }
{/* 添加用户 & 修改用户弹出的浮层 */ }
);
}
export default connect()(User);
2.完成component页面:userList userSearch userModal
import React, { PropTypes } from 'react';
import { Table, message, Popconfirm } from 'antd';
const UserList = ({total,current,loading,dataSource}) => {
const columns=[{}];
const pagination = {};
return (
record.id}
pagination={pagination} />
);
}
export default userList;
注意:routes可以简单的理解是页面组件 components可以理解是模块组件,
routes里面引用多个components的组件
四.更新state:—》model/user里面完成 reducers 计算新的state
export default {
namespace: 'user',
reducers: {
// 使用静态数据返回
querySuccess(state){
const mock = {
total: 3,
current: 1,
loading: false,
list: [
{
id: 1,
name: '张三',
age: 23,
address: '成都',
},
]
};
return {...state, ...mock, loading: false};
},
createSuccess(){},
deleteSuccess(){},
updateSuccess(){},
}
}
五.关联Model数据:把model中计算的新state的值 关联到组件
=》返回routes/user
import { connect } from 'dva';
function User({ location, dispatch, user }) {
//Model中state 赋值到 { loading, list, total, current,currentItem, modalVisible, modalType}
const {
loading, list, total, current,
currentItem, modalVisible, modalType
} = user;
const userListProps = {
dataSource: list,
total,
loading,
current,
};
const userSearchProps = {};
const userModalProps = {};
return (
{/* 用户筛选搜索框 */}
{/* 用户信息展示列表 */ }
{/* 添加用户 & 修改用户弹出的浮层 */ }
);
}
//指定订阅数据,这里关联了model中 user
function mapStateToProps({ user }) {
return {user};
}
//建立数据关联关系
const User = connect(mapStateToProps)(User);
export default User;
注意:1.记得 index.js 入口文件处要要关联model :app.model(require('./models/user')); 否则获取不到数据
2.数据关联以后,就可以通过 props 访问到 model 的数据了,
而 UserList 展示组件的数据,也是 routes/user 组件 通过 props 传递的过来的。
六.数据关联后,就要更新reducer的数据 :model/user
调用 reducers呢,就是需要发起一个 action。 这边可以使用Subscription 订阅 监听路由的改变
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
if (location.pathname === '/user') {
dispatch({
type: 'querySuccess',
payload: {}
});
}
});
},
},
七.异步处理:返回model effects: {...}的操作
import { hashHistory } from 'dva/router';
import { query } from '../services/user';
export default {
namespace: 'user',
reducers: {
showLoading(state, action){
return { ...state, loading: true };
},// 控制加载状态的 reducer
showModal(){}, // 控制 Modal 显示状态的 reducer
hideModal(){},
// 使用服务器数据返回
querySuccess(state, action){
return {...state, ...action.payload, loading: false};
},
},
effects: {
*query({ payload }, { select, call, put }) {
yield put({ type: 'showLoading' });
const { data } = yield call(query);
if (data) {
yield put({
type: 'querySuccess',
payload: {
list: data.data,
total: data.page.total,
current: data.page.current
}
});
}
},
}
}
八.后端数据请求
1.代理
在.webpackrc 文件里面加
"proxy": {
"/api": {
"target": "http://localhost:8080",
"changeOrigin": true,
"pathRewrite": {
"^/api" : ""
}
}
},
或是 安装 roadhorg 在.roadhogrc.js里面做代理
2.在services/user 做交互
/*与后台系统的交互)
request 是我们封装的一个网络请求库
qs是一个npm仓库所管理的包 对请求返回的数据进行处理
*/
import request from '../utils/request';
import qs from 'qs';
export async function query(params) {
return request(`/api/user?${qs.stringify(params)}`);
}
执行上面8步骤就差不多
六.补充
(一).dva 2.0中如何使用代码进行路由跳转
[email protected] 让路由变得更简单,最大特点就是可以路由嵌套。由于 dva 将react-router-dom
和react-router-redux
都封装到了 dva/router 中,在使用 [email protected] 和 redux 里面的东西时只需引入 dva/router 这个包即可。[email protected] 文档 API
dva 中使用 router4.0
router.js:
import React from 'react';
import { Router, Route, Switch,routerRedux } from 'dva/router';
import BasicLayout from '../layouts/BasicLayout'
const { ConnectedRouter } = routerRedux;
function RouterConfig({ history }) {
return (
);
}
export default RouterConfig;
Route 为 react-router-dom 内的标签
ConnectedRouter 为 react-router-redux 内的对象 routerRedux 的标签,作用相当于 react-router-dom 中的 BrowserRouter 标签,作用为连接 redux 使用。
1.路由跳转
引入 dva/router,使用 routerReux 对象的 push 方法控制,值为要跳转的路由地址,与根目录下 router.js 中配置的路由地址是相同的。routerReux 就是上面 dva/router 第二个导出的 react-router-redux 包对象。
此处示例为跳转到 /user
路由。
// models > app.js
import { routerRedux } from 'dva/router';
export default {
// ...
effects: {
// 路由跳转
* redirect ({ payload }, { put }) {
yield put(routerRedux.push('/user'));
},
}
// ...
}
2.携带参数
有时路由的跳转还需要携带参数。
3.传参:
routerRedux.push 方法的第二个参数填写参数对象。此处示例表示跳转到 /user 路由,并携带参数 {name: 'dkvirus', age: 20}。
// models > app.js
import { routerRedux } from 'dva/router';
export default {
// ...
effects: {
// 路由跳转
* redirect ({ payload }, { put }) {
yield put(routerRedux.push('/user', {name: 'dkvirus', age: 20}));
},
}
// ...
}
接收参数:
// models > user.js
export default {
subscriptions: {
/**
* 监听浏览器地址,当跳转到 /user 时进入该方法
* @param dispatch 触发器,用于触发 effects 中的 query 方法
* @param history 浏览器历史记录,主要用到它的 location 属性以获取地址栏地址
*/
setup ({ dispatch, history }) {
history.listen((location) => {
console.log('location is: %o', location);
console.log('重定向接收参数:%o', location.state)
// 调用 effects 属性中的 query 方法,并将 location.state 作为参数传递
dispatch({
type: 'query',
payload: location.state,
})
});
},
},
effects: {
*query ({ payload }, { call, put }) {
console.log('payload is: %o', payload);
}
}
// ...
}
在 user.js 中 subscriptions 属性会监听路由。当 app.js 中通过代码跳转到 /user
路由,models>user.js>subscriptions 属性中的 setup 方法会被触发,location 记录着相关信息。打印如下:
location is: Object
hash: ""
key: "kss7as"
pathname: "/user"
search: ""
state: {name: "bob", age: 21}
重定向接收参数:Object
age:21
name:"bob"
可以看到 location.state
就是传递过来的参数。在 subscriptions 中可以使用 dispatch 触发 effects 中的方法同时传递参数。
(三). 解决组件动态加载问题的 util 方法
import dynamic from 'dva/dynamic';
opts 包含:
- app: dva 实例,加载 models 时需要
- models: 返回 Promise 数组的函数,Promise 返回 dva model
- component:返回 Promise 的函数,Promise 返回 React Component
使用如下:
mport dynamic from 'dva/dynamic'
{
routeArr.map((item, key) => {
return
})
}
(四).React.Component与React.PureComponent的区别
React15.3中新加了一个 PureComponent 类,顾名思义, pure 是纯的意思, PureComponent 也就是纯组件,取代其前身 PureRenderMixin , PureComponent 是优化 React 应用程序最重要的方法之一,易于实施,只要把继承类从 Component 换成 PureComponent 即可,可以减少不必要的 render操作的次数,从而提高性能,而且可以少写 shouldComponentUpdate 函数,节省了点代码。
React.PureComponent 与 React.Component 几乎完全相同,但 React.PureComponent 通过prop和state的浅对比来实现 shouldComponentUpate()。
如果React组件的 render() 函数在给定相同的props和state下渲染为相同的结果,在某些场景下你可以使用 React.PureComponent 来提升性能。
七.参考
- Dva-React 应用框架在蚂蚁金服的实践
- dva.js 知识导图
- redux-saga
- UMI
- dva理论到实践——帮你扫清dva的知识盲点
- History API
- redux docs 中文
- roadhog介绍
- 基于dva-cli&antd的react项目实战
- 10分钟 让你dva从入门到精通
- 上手快速
- async 函数的含义和用法
- 基于 dva 创建 antd-mobile 的项目
八.案例
1.后台管理系统
你可能感兴趣的:(React+redux+ant,design+dva)