2017-07-10 14:47:44 weiyongliang_813 阅读数 3637

昨天才用create-react-app创建了react的开发环境,以为今后就用它来创建开发环境咯,谁知道才用了一天,才发下dva也是用来创建react开发环境的。
dva是淘宝团队弄的,为了支持国货,赶紧试试看咋用吧
第一步:安装 dva-cli

cnpm install dva-cli -g

第二步:采用dva来创建项目:

dva new react_two
cd react_two

用webstorme打开react_two项目:文件解构大概如下:
这里写图片描述

第三步:npm start 启动起来访问路径:http://localhost:8000/ 界面效果如下:

这里写图片描述

我个去哦,界面效果跟用create-react-app创建的工程的界面效果基本一样。
其实原理跟create-react-app里创建工程的react-scripts原理基本一样,在此就不分析咯,其实就是换汤不换药而已。

2019-05-29 19:17:26 qq_33559093 阅读数 828

先初始化一个react native的项目

npm install -g yarn react-native-cli

react-native init AwesomeProject

准备安装dva 、react-navigation

1.yarn add  dva-core

2.yarn add react-redux

3.yarn add react-navigation

4.yarn add react-native-gesture-handler

5.react-native link react-native-gesture-handler

然后是集成步骤

1.集成dva,使用dva-core和react-redux,新建app文件夹

app文件夹中新建.root.js

import * as React from 'react';
import indexModel from './models/index'
import { Provider  } from 'react-redux';
import App from './routes'//    这个是react-navigation路由文件

import { create } from 'dva-core';

const models = [indexModel];

const app = create(); // 创建dva实例,可传递配置参数。https://dvajs.com/api/#app-dva-opts

models.forEach((o) => { // 装载models对象
  app.model(o);
});

app.start(); // 实例初始化

const store = app._store; // 获取redux的store对象供react-redux使用

export default class Container extends React.Component {
  render() {
    return (
      <Provider store={store}>//    注入dva
        <App/>
      </Provider>
    );
  }
}

2.编写路由,react-navigation

app文件夹下新建routes.js

import React, { PureComponent } from 'react'
import {
  TabBarBottom, addNavigationHelpers, createSwitchNavigator,
  createBottomTabNavigator, createStackNavigator, createAppContainer
} from 'react-navigation'
import AuthLoadingScreen from './components/loading/index'
import {StatusBar, View, Platform} from "react-native";

import Home from './pages/Home/Home'
import Order from './pages/Order/Order'
import Get from './pages/Get/Get'
import Register from './pages/Register/index'
import Establish from './pages/Establish/index'
import Restore from './pages/Restore/index'

const AuthStack = createStackNavigator(
  {
    Register: Register,
    Establish: Establish,
    Restore: Restore
  },
);
const HomeNavigator = createBottomTabNavigator(
  {
    Home: { screen: Home },
    Order: { screen: Order },
    Get: { screen: Get }
  }
);

const AppNavigation = createSwitchNavigator(
  {
    App: HomeNavigator,
    Auth: AuthStack,
    AuthLoading: AuthLoadingScreen
  },
  {
    initialRouteName: 'AuthLoading',
  }
);

const App = createAppContainer(AppNavigation);

export default App

3.链接项目

根目录下的index.js

import {AppRegistry} from 'react-native';
import App from './app/root';
import {name as appName} from './app.json';

AppRegistry.registerComponent(appName, () => App);

最后,如何在组件中使用dva,点我的另外一篇文章----->点这里

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

 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.后台管理系统

2019-05-31 16:46:49 q2235141 阅读数 20

一、什么是dva

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

三、项目结构

在这里插入图片描述

四、dva数据流程图

在这里插入图片描述

五、Model

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

  • 属性说明

namespace: 当前 Model 的名称。整个应用的 State,由多个Model 的 State 以 namespace 为 key 合成

state: 该 Model 当前的状态。数据保存在这里,直接决定了视图层的输出

reducers: Action 处理器,处理同步动作,用来算出最新的 State

effects:Action 处理器,处理异步动作

subscription:订阅state

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 / dispatch

Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。表示操作事件,可以是同步,可以是异步

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

3.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
	}
}

4.Effect

用于处理异步操作和业务逻辑,不直接修改 state,简单的来说,就是获取从服务端获取数据,并且发起一个 action 交给 reducer 的地方,基于 Redux-saga 实现。

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)

5.subscription

subscription 是订阅,用于订阅一个数据源,然后根据需要 dispatch 相应的 action。在 app.start() 时被执行,数据源可以是当前的时间、当前页面的url、服务器的 websocket 连接、history 路由变化等

  subscriptions: {
    setup({ dispatch, history }) {
      history.listen(({ pathname }) => {
        if (pathname === '/users') {
              dispatch({
                type: 'users/fetch',
              })
        	}
      })
    }
  }
2018-06-28 23:49:09 sha1024 阅读数 6836

个人博客


前言:

原谅我直接跳过react的基础,直接就讲react+dva的实际应用,因为直接从项目结构来讲,我觉得学习的成本更低,开发的速度更快,当然有时间的话还是建议冲react的基础学起。

react的参考资料:
建议先从React 入门实例教程开始学
react的全家桶系列也不错

dva的参考资料:
dva的官方指南有很多不错的脚手架
ant design组合ant design UI可以说非常方便

例子
实现效果
首页
修改页面

前提条件

  • 确保 node 版本是 6.5 +
  • 用 cnpm 或 yarn 能节约你安装依赖的时间

第一步:

打开cmd,切换到你要安装的目录,使用npm安装dva-cli

$ npm i dva-cli@0.7 -g
$ dva -v
0.7.0

然后可以在命令行输入命令创建应用

$ dva new user-dashboard
$ cd user-dashboard 

第二步,

配置 antd 和 babel-plugin-import
babel-plugin-import 主要是用于按需引入 antd 的 JavaScript 和 CSS,这样打包出来的文件不至于太大。详情请看dva

$ npm i antd --save
$ npm i babel-plugin-import --save-dev

如果dva的版本是在0.7一下的话是没有.roadhogrc文件,修改 .roadhogrc,在 “extraBabelPlugins” 里加上,0.7一下的话是修改.webpackrc文件:

["import", { "libraryName": "antd", "style": "css" }]

第三步,

配置代理,能通过 RESTFul 的方式访问
修改 .roadhogrc,加上 “proxy” 配置,或者修改.webpackrc文件也可:
可以通过http://localhost:8000/api/users来查看json数据

"proxy": {
  "/api": {
    "target": "http://jsonplaceholder.typicode.com/",
    "changeOrigin": true,
    "pathRewrite": { "^/api" : "" }
  }
},

然后启动应用:可以新开一个命令行窗口

$ npm start

然后系统就会自动在浏览器中打开页面,也可以访问http://localhost:8000/
看到这个图片就可以下一步了

第四步,

生成 users 路由

dva g route users

输入这个命令就会在src的routes生成两个文件,一个User.js,一个是User.css,然后访问 http://localhost:8000/#/users

第五步,

构造 users model 和 service
同上,用dva的命令生成文件

dva g model users

然后修改 src/models/users.js :

import * as usersService from '../services/users';

export default {
  namespace: 'users',
  state: {
    list: [],
    total: null,
  },
  reducers: {
    save(state, { payload: { data: list, total } }) {
      return { ...state, list, total };
    },
  },
  effects: {
    *fetch({ payload: { page } }, { call, put }) {
      const { data, headers } = yield call(usersService.fetch, { page });
      yield put({ type: 'save', payload: { data, total: headers['x-total-count'] } });
    },
  },
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname, query }) => {
        if (pathname === '/users') {
          dispatch({ type: 'fetch', payload: query });
        }
      });
    },
  },
};

在目标文件夹中新增 src/services/users.js:

import request from '../utils/request';

export function fetch({ page = 1 }) {
  return request(`/api/users?_page=${page}&_limit=5`);
}

由于我们需要从 response headers 中获取 total users 数量,所以需要改造下 src/utils/request.js:

import fetch from 'dva/fetch';

function checkStatus(response) {
  if (response.status >= 200 && response.status < 300) {
    return response;
  }

  const error = new Error(response.statusText);
  error.response = response;
  throw error;
}

/**
 * Requests a URL, returning a promise.
 *
 * @param  {string} url       The URL we want to request
 * @param  {object} [options] The options we want to pass to "fetch"
 * @return {object}           An object containing either "data" or "err"
 */
export default async function request(url, options) {
  const response = await fetch(url, options);

  checkStatus(response);

  const data = await response.json();

  const ret = {
    data,
    headers: {},
  };

  if (response.headers.get('x-total-count')) {
    ret.headers['x-total-count'] = response.headers.get('x-total-count');
  }

  return ret;
}

第六步,

添加界面,让用户列表展现出来
用 dva-cli 生成 component:

$ dva g component Users/Users

然后修改生成出来的 src/components/Users/Users.js 和 src/components/Users/Users.css,并在 src/routes/Users.js 中引用他。
User.js

import React from 'react';
import { connect } from 'dva';
import { Table, Pagination, Popconfirm, Button } from 'antd';
import { routerRedux } from 'dva/router';
import styles from './Users.css';
import { PAGE_SIZE } from '../../constants';
import UserModal from './UserModal';

function Users({ dispatch, list: dataSource, loading, total, page: current }) {

  function deleteHandler(id) {
    dispatch({
      type: 'users/remove',
      payload: id,
    });
  }

  function editHandler(id, values) {
        dispatch({
            type: 'users/patch',
            payload: { id, values },
        });
      }

  function pageChangeHandler(page) {
    dispatch(routerRedux.push({
	pathname: '/users',
    query: { page },
	 }));
  }

  function createHandler(values) {
        dispatch({
            type: 'users/create',
            payload: values,
          });
     }

  const columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
      render: text => <a href="">{text}</a>,
    },
    {
      title: '邮箱',
      dataIndex: 'email',
      key: 'email',
    },
    {
      title: '地址',
      dataIndex: 'website',
      key: 'website',
    },
    {
      title: '操作',
      key: 'operation',
      render: (text, record) => (
        <span className={styles.operation}>
            <UserModal record={record} onOk={editHandler.bind(null, record.id)}>
            <a>修改</a>
              </UserModal>
            <Popconfirm title="Confirm to delete?" onConfirm={deleteHandler.bind(null, record.id)}>
            <a href="">删除</a>
          </Popconfirm>
        </span>
      ),
    },
  ];

  return (
    <div className={styles.normal}>
      <div>
          <div className={styles.create}>
            <UserModal record={{}} onOk={createHandler}>
                <Button type="primary">增加用户</Button>
            </UserModal>
          </div>
        <Table
          columns={columns}
          dataSource={dataSource}
		  loading={loading}
          rowKey={record => record.id}
          pagination={false}
        />
        <Pagination
          className="ant-table-pagination"
          total={total}
          current={current}
          pageSize={PAGE_SIZE}
		  onChange={pageChangeHandler}
        />
      </div>
    </div>
  );
}

function mapStateToProps(state) {
  const { list, total, page } = state.users;
  return {
	loading: state.loading.models.users,
    list,
    total,
    page,
  };
}

export default connect(mapStateToProps)(Users);

User.css

.normal {
}
.create {
  margin-bottom: 1.5em;
}

.operation a {
  margin: 0 .5em;
}

这边主要是对 model 进行了微调,加入了 page 表示当前页
由于 components 和 services 中都用到了 pageSize,所以提取到 src/constants.js
改完后,切换到浏览器,应该能看到带分页的用户列表

第七步,

添加 layout

  1. 添加 layout 布局,使得我们可以在首页和用户列表页之间来回切换。
    2.添加布局,src/components/MainLayout/MainLayout.js 和 CSS 文件
    在 src/routes 文件夹下的文件中引用这个布局
import React from 'react';
import { Router, Route } from 'dva/router';
import IndexPage from './routes/IndexPage';

import Users from "./routes/Users.js";

function RouterConfig({ history }) {
  return (
    <Router history={history}>
      <Route path="/" component={IndexPage} />
      <Route path="/users" component={Users} />
    </Router>
  );
}

export default RouterConfig;

第八步,

通过 dva-loading 处理 loading 状态
dva 有一个管理 effects 执行的 hook,并基于此封装了 dva-loading 插件。通过这个插件,我们可以不必一遍遍地写 showLoading 和 hideLoading,当发起请求时,插件会自动设置数据里的 loading 状态为 true 或 false 。然后我们在渲染 components 时绑定并根据这个数据进行渲染。

先安装 dva-loading :

npm i dva-loading --save

修改 src/index.js 加载插件,在对应的地方加入下面两句:

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

然后在 src/components/Users/Users.js 里绑定 loading 数据:

loading: state.loading.models.users,

具体参考这个 例子

第九步,

处理分页
只改一个文件 src/components/Users/Users.js 就好。

处理分页有两个思路:

  1. 发 action,请求新的分页数据,保存到 model,然后自动更新页面
  2. 切换路由 (由于之前监听了路由变化,所以后续的事情会自动处理)
    我们用的是思路 2 的方式,好处是用户可以直接访问到 page 2 或其他页面。

参考这个 例子

第10步,

用户的修改,删除,增加都是对三个文件的操作

  1. service, 修改 src/services/users.js:
import request from '../utils/request';
import { PAGE_SIZE } from '../constants';

export function fetch({ page }) {
  return request(`/api/users?_page=${page}&_limit=${PAGE_SIZE}`);
 }
 //删除
export function remove(id) {
  return request(`/api/users/${id}`, {
    method: 'DELETE',
  });
}
//修改
export function patch(id, values) {
  return request(`/api/users/${id}`, {
    method: 'PATCH',
    body: JSON.stringify(values),
    headers: {
    'Content-Type': 'application/json',
      'Accept': 'application/json',
  },
  });
}
//新增
export function create(values) {
   return request('/api/users', {
       method: 'POST',
        body: JSON.stringify(values),
      });
  }

  1. model, 修改 src/models/users.js:
import * as usersService from '../services/users';

export default {
  namespace: 'users',
  state: {
    list: [],
    total: null,
    page: null,
  },
  reducers: {
    save(state, { payload: { data: list, total, page } }) {
      return { ...state, list, total, page };
    },
  },
  effects: {
	  //分页
    *fetch({ payload: { page = 1 } }, { call, put }) {
      const { data, headers } = yield call(usersService.fetch, { page });
      yield put({
        type: 'save',
        payload: {
          data,
          total: parseInt(headers['x-total-count'], 10),
          page: parseInt(page, 10),
        },
      });
    },
	//删除
  *remove({ payload: id }, { call, put }) {
      yield call(usersService.remove, id);
       yield put({ type: 'reload' });
  },
  //修改
*patch({ payload: { id, values } }, { call, put }) {
    yield call(usersService.patch, id, values);
    yield put({ type: 'reload' });
},
//新增
*create({ payload: values }, { call, put }) {
        yield call(usersService.create, values);
       yield put({ type: 'reload' });
      },
  *reload(action, { put, select }) {
      const page = yield select(state => state.users.page);
       yield put({ type: 'fetch', payload: { page } });
  },

  },
  subscriptions: {
    setup({ dispatch, history }) {
      return history.listen(({ pathname, query }) => {
        if (pathname === '/users') {
          dispatch({ type: 'fetch', payload: query });
        }
      });
    },
  },
};

  1. component, 修改 src/components/Users/Users.js,替换 deleteHandler 内容:
import React from 'react';
import { connect } from 'dva';
import { Table, Pagination, Popconfirm, Button } from 'antd';
import { routerRedux } from 'dva/router';
import styles from './Users.css';
import { PAGE_SIZE } from '../../constants';
import UserModal from './UserModal';

function Users({ dispatch, list: dataSource, loading, total, page: current }) {
//删除
  function deleteHandler(id) {
    dispatch({
      type: 'users/remove',
      payload: id,
    });
  }
//修改
  function editHandler(id, values) {
        dispatch({
            type: 'users/patch',
            payload: { id, values },
        });
      }
//分页
  function pageChangeHandler(page) {
    dispatch(routerRedux.push({
	pathname: '/users',
    query: { page },
	 }));
  }
//新增
  function createHandler(values) {
        dispatch({
            type: 'users/create',
            payload: values,
          });
     }

  const columns = [
    {
      title: '姓名',
      dataIndex: 'name',
      key: 'name',
      render: text => <a href="">{text}</a>,
    },
    {
      title: '邮箱',
      dataIndex: 'email',
      key: 'email',
    },
    {
      title: '地址',
      dataIndex: 'website',
      key: 'website',
    },
    {
      title: '操作',
      key: 'operation',
      render: (text, record) => (
        <span className={styles.operation}>
            <UserModal record={record} onOk={editHandler.bind(null, record.id)}>
            <a>修改</a>
              </UserModal>
            <Popconfirm title="Confirm to delete?" onConfirm={deleteHandler.bind(null, record.id)}>
            <a href="">删除</a>
          </Popconfirm>
        </span>
      ),
    },
  ];

  return (
    <div className={styles.normal}>
      <div>
          <div className={styles.create}>
            <UserModal record={{}} onOk={createHandler}>
                <Button type="primary">增加用户</Button>
            </UserModal>
          </div>
        <Table
          columns={columns}
          dataSource={dataSource}
		  loading={loading}
          rowKey={record => record.id}
          pagination={false}
        />
        <Pagination
          className="ant-table-pagination"
          total={total}
          current={current}
          pageSize={PAGE_SIZE}
		  onChange={pageChangeHandler}
        />
      </div>
    </div>
  );
}

function mapStateToProps(state) {
  const { list, total, page } = state.users;
  return {
	loading: state.loading.models.users,
    list,
    total,
    page,
  };
}

export default connect(mapStateToProps)(Users);

以上就是react+dva+antd实现的一个简单的增删改的操作例子。

dva+react+ant.design

阅读数 1062

react-dva学习

阅读数 7

react环境搭建(基于dva)

博文 来自: Asuran18

react项目 初始dva

阅读数 100

React dva Model案例

阅读数 263

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