2019-11-20 13:58:43 qq_17025903 阅读数 18

一、基本概念

dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,还额外内置了

react-router 和 fetch,所以也可以理解为一个轻量级的应用框架。

  • 易学易用,仅有 6 个 api,对 redux 用户尤其友好,配合 umi 使用后更是降低为 0 API
  • elm 概念,通过 reducers, effects 和 subscriptions 组织 model
  • 插件机制,比如 dva-loading 可以自动处理 loading 状态,不用一遍遍地写 showLoading 和 hideLoading
  • 支持 HMR,基于 babel-plugin-dva-hmr 实现 components、routes 和 models 的 HMR
     

个人理解 :简化了React的setState跨组件传输数据的耦合,数据通过mapStateToProps

就可以共享数据,并且比Redux传输数据操作要更简单,可以自动处理等待状态,如果会

Redux入门可以更迅速 ,更快的上手。

二、代码详解

可以通过  npm install  dva-cli  或  yarn add dva-cli 安装

创建model文件夹和viewer.js

export default {

  namespace: "viewer",

  state: {
    cesiumViewer:undefined
  },

  subscriptions: {
    setup({ dispatch, history }) {  // eslint-disable-line
    },
  },
  effects: {
    * fetch({ payload }, { call, put }) {  // eslint-disable-line
      yield put({ type: "save" });
    }
  },
  reducers: {
    /**
     *
     * @param {*}state 状态
     * @param {*}action 动作
     * @returns {*} 结果
     */
    save(state, action) {
      return { ...state, ...action.payload };
    },
    /**
     *
     * @param {*}state 状态
     * @param {*}action 动作
     * @returns {*} 结果
     */
    changeAction(state, action){
      return { ...state, ...action.payload };
    }
  },
}

namespace:可以通过空间名找到对应的model下的 viewer.js
state: {cesiumViewer:undefined} 存储共享的数据
subscriptions:常用来监听浏览器地址输入的参数
effects:异步处理
reducers:同步处理
save:自定义方法里的 state为当前的数据,action.payload为传过来要改变的数据

调用save方法只需要用  dispatch type类型为 namespace(viewer) 和自定义方法名(save)  payload 传递改变的参数

    this.props.dispatch({
      type: "viewer/save",
      payload: {
        cesiumViewer:123456
      },
    });

save 加上回调和异常处理则为

    this.props.dispatch(
      {
        type: "viewer/save",
        payload: {
           cesiumViewer:123456
        },
        callback:(result)=>{
          console.log(result)
        },
        errorcallback:()=>{
          message.warning("服务异常");
        }
      }
    )

需要拿到共享参数则为 this.props.viewer.cesiumViewer 就可以拿到 viewer 空间下的共享数据

function mapStateToProps(state) {
  return {
    viewer:state.viewer,
  };
}
export default connect(mapStateToProps)(绑定的class);

三、Dva原理图

数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。

个人理解:通过URL调用渲染组件初始化dom的时候,会通过connect(mapStateToProps) 加载共享数据,并且调用dispatch会触发action 进行同步或异步操作或者监听地址栏是否传入参数的处理。

 

2018-03-21 10:40:04 cater_bjut 阅读数 370

react+dva

2018-08-23 17:10:32 gao_xu_520 阅读数 3297

 dva 是由阿里架构师 sorrycc 带领 team 完成的一套前端框架,在作者的 github 里是这么描述它的:“dva 是 react 和 redux 的最佳实践”。


一.介绍

1.What's dva ?

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装。dva 是 react 和 redux 的最佳实践。最核心的是提供了 app.model 方法,用于把 reducer, initialState, action, saga 封装到一起。官网    dva = React-Router + Redux + Redux-saga

2.安装

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

 成功。

3.项目结构

二.概念

下面都是官网的知识点

(1).数据流向

数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State,所以在 dva 中,数据流向非常清晰简明,并且思路基本跟开源社区保持一致(也是来自于开源社区)。

(2).Model

model 是 dva 中最重要的概念,Model 非 MVC 中的 M,而是领域模型,用于把数据相关的逻辑聚合到一起,几乎所有的数据,逻辑都在这边进行处理分发

1.State

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

2.Action   :type dispatch = (a: Action) => Action

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))

3.dispatch 函数: type dispatch = (a: Action) => Action

dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
在 dva 中,connect Model 的组件通过 props 可以访问到 dispatch,可以调用 Model 中的 Reducer 或者 Effects,常见的形式如:

dispatch({
  type: 'user/add', // 如果在 model 外调用,需要添加 namespace
  payload: {}, // 需要传递的信息
});

4.reducer:   type Reducer<S, A> = (state: S, action: A) => S

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,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用。 

5.Effect

用于处理异步操作和业务逻辑,不直接修改 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

6.subscription

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'}) });
    },
  }
});

(3).Router   表示路由配置信息

项目中的 router.js。

这里的路由通常指的是前端路由,由于我们的应用现在通常是单页应用,所以需要前端代码来控制路由逻辑,通过浏览器提供的 History API 可以监听浏览器url的变化,从而控制路由相关操作。

Route Component 表示 Router 里匹配路径的 Component,通常会绑定 model 的数据

import { connect } from 'dva';

function App() {
  return <div>App</div>;
}

function mapStateToProps(state) {
  return { todos: state.todos };
}

export default connect(mapStateToProps)(App);

三.Dva API

官网.

1.app = dva(Opts):创建应用,返回 dva 实例。(注:dva 支持多实例)​

opts 包含:
    history:指定给路由用的 history,默认是 hashHistory      关于react-router中的hashHistorybrowserHistory的区别大家可以看: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,
});

 2.app.use(Hooks):配置 hooks 或者注册插件。  

这里最常见的就是dva-loading插件的配置,就是引入第三方插件的时候使用

import createLoading from 'dva-loading';
...
app.use(createLoading(opts));

3.app.model()

model 是 dva 中最重要的概念。 这个是你数据逻辑处理,数据流动的地方。

(1).5个属性

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

总结:

Model 对象的属性

  1. namespace: 当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成
  2. state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出
  3. reducers: Action 处理器,处理同步动作,用来算出最新的 State
  4. effects:Action 处理器,处理异步动作

4.app.unmodel(namespace)   取消 model 注册

5.app.router(({ history, app }) => RouterConfig)
注册路由表,我们做路由跳转的地方。一般是这样写:

import { Router, Route } from 'dva/router';
app.router(({ history }) => {
  return (
    <Router history={history}>
      <Route path="/" component={App} />
    <Router>
  );
});

推荐把路由信息抽成一个单独的文件,这样结合 babel-plugin-dva-hmr 可实现路由和组件的热加载。

比如:app.router(require('./router'));

但是如果你的项目特别的庞大,我们就要考虑到相应的性能的问题,就要router按需加载的写法。 

6.app.start(selector)

启动应用,项目跑起来
app.start('#root');

四.安装插件

1.使用 antd

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" }]
  ]
}

2.安装antd-mobile

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 (
			<Router history={history}>
			  	<Switch>
			    	<Route path="/user" exact component={User} />
			  	</Switch>
			</Router>
		);
	}

	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 (
			<div className={styles.normal}>
			   {/* 用户筛选搜索框 */}
				<UserSearch {...userSearchProps} />
				{/* 用户信息展示列表 */ }
				<UserList {...userListProps} />
				{/* 添加用户 & 修改用户弹出的浮层 */ }
				<UserModal {...userModalProps} />
			</div>
		);
	}


	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 (
			<div>
		      	<Table  columns={columns}
			        dataSource={dataSource}
			        loading={loading}
			        rowKey={record => record.id}
			        pagination={pagination} />
		    </div>
		);
	}

	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 (
			<div className={styles.normal}>
			   {/* 用户筛选搜索框 */}
				<UserSearch {...userSearchProps} />
				{/* 用户信息展示列表 */ }
				<UserList {...userListProps} />
				{/* 添加用户 & 修改用户弹出的浮层 */ }
				<UserModal {...userModalProps} />
			</div>
		);
	}
    //指定订阅数据,这里关联了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中如何使用代码进行路由跳转

react-router@4.0 让路由变得更简单,最大特点就是可以路由嵌套。由于 dva 将react-router-domreact-router-redux都封装到了 dva/router 中,在使用 react-router@4.0 和 redux 里面的东西时只需引入 dva/router 这个包即可。react-router@4.0 文档 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 (
		<ConnectedRouter history={history}>
        	<Route path="/" component={BasicLayout} />
      	</ConnectedRouter>
	);
}

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 <Route key={key} exact path={item.path} component={dynamic({ //保证路由的唯一性 exact key
		app,
		model: item.models,
		component: item.component,
	})} />
})

}

(四).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 来提升性能

七.参考

  1. Dva-React 应用框架在蚂蚁金服的实践
  2.   dva.js 知识导图     
  3. redux-saga   
  4.  UMI       
  5. dva理论到实践——帮你扫清dva的知识盲点      
  6. History API   
  7. redux docs 中文
  8. roadhog介绍
  9. 基于dva-cli&antd的react项目实战
  10. 10分钟 让你dva从入门到精通
  11. 上手快速
  12. async 函数的含义和用法
  13. 基于 dva 创建 antd-mobile 的项目

八.案例

1.后台管理系统

2017-12-28 16:39:39 bbsyi 阅读数 2819

 

作者:zhenhua-lee
链接:https://www.zhihu.com/question/51831855/answer/225446217
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  1. 框架: dva是个框架,集成了redux、redux-saga、react-router-redux、react-router
  2. 快速初始化: 可以快速实现项目的初始化,不需要繁琐地配置
  3. 简化开发:将initState、saga、reducer集成到一个model里面统一管理,避免文件散落在各个文件里面,便于快速查找与开发
  4. 简洁的API:整个项目中只有dva、app.model、app.router、app.use、app.start几个API
  5. 无缝对接:跟react的生态没有冲突,例如可以直接使用redux devtool工具
  6. 动态机制:app.start以后,仍然可以注册model,灵活性较高

再说说个人觉得不太爽的地方吧:

  1. namespace不统一: dva中的action.type格式是namespace/XXX,且在model中不需要添加前缀namespace,但是在组件中dispatch,却需要添加prefix
  2. action问题:action会散落在两个地方,一个是saga里面,另外一个是component dispatch的时候,当然这个问题在使用redux-saga的时候就会存在,只是dva仍然没有很好地统一起来。

 

2018-08-06 18:26:30 annroy 阅读数 3498

Dva整体理解


前言

README.md

欢迎阅读,本文档将带你了解dva的整体思路。

介绍

应用 dva 框架搭建平台,dva 是基于 redux 最佳实践实现的framework,简化使用 redux 和redux-saga 时很多繁杂的操作。 dva 文档比较健全,可以访问https://github.com/dvajs/dva-docs/blob/master/v1/zh-cn/tutorial/01-%E6%A6%82%E8%A6%81.md查看更多内容,其中会介绍整个框架的开发思路。

 

关于dva

dva 是基于现有应用架构 (redux + react-router + redux-saga 等)的一层轻量封装;

数据流向: 数据的改变发生通常是通过:

  • 用户交互行为(用户点击按钮等)
  • 浏览器行为(如路由跳转等)触发的

当此类行为会改变数据的时候可以通过 dispatch 发起一个 action ,如果是同步行为会直接通过Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变State 。 所以在 dva 中,数据流向非常清晰简明,如上图。

 

关于Redux-saga

Redus-saga 是一个 redux 的中间件,主要用来简便而优雅的处理 redux 应用里的副作用(side effect相对于pure function这类概念而言的)。它之所以可以做到这一点主要是使用了ES6 里的一个语法: Generator 。使用 Generator 可以像写同步的代码一样编写异步代码,这样更加容易测试。

 

准备

本文档第一部分将带领大家获得项目源码。首先需要的是将 gitlad 上的远程项目仓库克隆到本地;

$ git clone http://gitlab.xxxx.com/xx/xxx.git

克隆完成后,本地目标文件夹里就会出现目标远程仓库的文件;

目录

本文档的第一部分将带领大家了解dva项目 目录结构。在下载源码之后,我们可以看到,整个系统的构建以及管理。目录如下:

工程结构如下:

● xxxx
 ○ public
 ○ src                          #项目源码目录
   ■ assets                     #静态资源
   ■ common                     #路由资源
   ■ components                 #项目组件
   ■ layouts                    #项目布局组件
   ■ models                     #数据模型
   ■ routes                     #路由组件(页面维度)
   ■ services                   #数据接口
   ■ utils                      #工具函数
   ■ index.js                   #入口文件
   ■ router.js                  #路由配置

开始

增加一个新功能

在上面的介绍中中我们已经对 dva 有了一定的认识,接下来我们会一起添加一个较为完善的功能,在实现的过程中,我们逐步完成以下内容:

  1. 添加顶层路由数据
  2. 添加路由
  3. 添加布局
  4. 定义service
  5. 设计Model
  6. 组件设计

添加顶层路由数据

第一步,我们会按照功能在 ./common/nav.js 添加路由数据:(部分代码)

// common/nav.js
{
  name: 'xxx',
  path: 'aaa',
  icon: 'global',
  page: 'ppp',
  grayscale: true,
  children: [
  {
     component: dynamicWrapper(app, ['aaa'], () =>  
                import('../routes/aaa')) 
  },
  //......
  ],
}

 

定义路由

路由决定进入 url 渲染哪些 Component 。 history 默认是 hashHistory;

// router.js
return (
  <LocaleProvider locale={zhCN}>
    <Router history={history}>
      <Switch>
        <Route path="/login" render={props => <XxxLayout {...props}   
        {...passProps} />} />
        <Route path="/sss" component={sss} />
        <Redirect exact from="/" to="/index" />
    </Switch>
   </Router>
  </LocaleProvider>
);

 

设计布局

路由添加完成后,设计布局,这些布局内容被抽象成组件,包含一些布局样式,用于组合其它组件搭建成页面。
本质上还是一种组件,将布局样式抽象成组件,能够保持子组件和父组件的独立性,不用在其中关联到布局信息。

注:若添加的是二级功能,不需要添加新的布局

 

定义service

接下来就是将请求相关抽离出来,单独放到 /services/ 中,进行统一维护管理;
如:

// services/xxx.js
export async function xxx(params) {
return     request(`xxx.json`, {
    method: 'GET',
});
}

设计Model

在完成项目基本的前期以后,我们将要开始设计 model ,在设计 model 之前,我们需要回顾一下项目的功能;
我们可以看出,这部分功能基本是围绕 以用户数据为基础 的操作,其中包含:

  • 用户信息的展示
  • 用户信息的操作

无论是多复杂的项目也基本上是围绕着数据的展示和操作,复杂一点的是组合了很多数据,有关联关系,只要分解开来, model 的结构层次依旧会很清晰,便于维护。
所以抽离 model 的原则就是抽离数据模型。

export default { 
namespace: 'xxx',
state: {
    helps: [],
    task: {},
    data: {
      list: [],
      pagination: {
         current: 1,
      },
    },
},
  effects: {
    * auth() {},
    * list() {};
    * detail() {},
    * xxList() {};
    * xxOperation() {},
  },
  reducers: {
    save(){}
  },
};

model 包含了关于操作的 state ,处理异步的 effect(dispatch action) ,以及修改 state 的reducers

视图设计

数据设计完成后,我们着手新增组件的编写,在 ./routes 中添加新增组件,根据 @connect 包裹组件的 props 进行操作的编写:

@connect(({ a1, a2 }) => ({
  a1,
  a2: ......,
}))
export default class xxx extends PureComponent {
  state = {
    page: 1,
    type: 1,
    //.....
  }

  componentDidMount() {
    const { dispatch } = this.props;
    dispatch({
      type: 'xx/auth',
    });
    dispatch({
      type: 'xx/list',
      payload: {
        page: 1,
        type: 1,
      },
    });
  }

 

组件设计

在确定了大体的设计方法以后,让我们来看看如何设计 dva 中的 React 组件。
React 应用是由一个个独立的 Component 组成的,我们在拆分 Component 的过程中要尽量让每个Component 专注做自己的事。

对组件分类,主要有两个好处:

  • 让项目的数据处理更加集中;
  • 让组件高内聚低耦合,更加聚焦;

 

注:本平台使用 ant design 进行组件的设计,避免样式表的复杂编写;

 

写在最后

dva将所有与数据操作相关的逻辑集中放在一个地方处理和维护,在数据跟业务状态交互比较紧密的场景下,会使我们的代码更加清晰可控。

对于一个大型管理系统,由于要进行大量的数据操作,在设计model时将不同类型的业务需求数据操作分开处理,便于维护。

项目的开发流程一般是从设计 model state 开始进行抽象数据,完成 component 后,将组件和model 建立关联,通过 dispatch 一个 action ,在 reducer 中更新数据完成数据同步处理;当需要从服务器获取数据时,通过 Effects 数据异步处理,然后调用 Reducer 更新全局 state 。是一个单向的数据流动过程。解决了 redux 中代码分散和重写问题,总之,Dva:Build redux application easier and better。

官方地址
Ant Design
Redux-saga 中文文档
dva-knowledgemap
dva: react application arch in ant financial

dva+react+ant.design

阅读数 1062

dva+react+antd

阅读数 491

没有更多推荐了,返回首页