文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
四、Dva 实践
4.1 抽离 Model
抽离 Model
,根据设计页面需求,设计相应的 Model
// models/users.js
// version1: 从数据维度抽取,更适用于无状态的数据
// version2: 从业务状态抽取,将数据与组件的业务状态统一抽离成一个 model
// 新增部分为在数据维度基础上,改为从业务状态抽取而添加的代码
export default {
namespace: 'users',
state: {
list: [],
total: null,
+ loading: false, // 控制加载状态
+ current: null, // 当前分页信息
+ currentItem: {}, // 当前操作的用户对象
+ modalVisible: false, // 弹出窗的显示状态
+ modalType: 'create', // 弹出窗的类型(添加用户,编辑用户)
},
// 异步操作
effects: {
*query(){},
*create(){},
*'delete'(){}, // 因为 delete 是关键字,特殊处理
*update(){},
},
// 替换状态树
reducers: {
+ showLoading(){}, // 控制加载状态的 reducer
+ showModel(){}, // 控制 Model 显示状态的 reducer
+ hideModel(){},
querySuccess(){},
createSuccess(){},
deleteSuccess(){},
updateSuccess(){},
}
}
4.2 设计组件
先设置容器组件的访问路径,再创建组件文件
4.2.1 容器组件
具有监听数据行为的组件,职责是绑定相关联的 model 数据,包含子组件;传入的数据来源于 model
import React, { Component, PropTypes } from 'react';
// dva 的 connect 方法可以将组件和数据关联在一起
import { connect } from 'dva';
// 组件本身
const MyComponent = (props)=>{};
// propTypes 属性,用于限制 props 的传入数据类型
MyComponent.propTypes = {};
// 声明模型传递函数,用于建立组件和数据的映射关系
// 实际表示 将 ModelA 这一个数据模型,绑定到当前的组件中,则在当前组件中,随时可以取到 ModelA 的最新值
// 可以绑定多个 Model
function mapStateToProps({ModelA}) {
return {ModelA};
}
// 关联 model
// 正式调用模型传递函数,完成模型绑定
export default connect(mapStateToProps)(MyComponent);
4.2.2 展示组件
展示通过 props
传递到组件内部数据;传入的数据来源于容器组件向展示组件的 props
import React, { Component, PropTypes } from 'react';
// 组件本身
// 所需要的数据通过 Container Component 通过 props 传递下来
const MyComponent = (props)=>{}
MyComponent.propTypes = {};
// 并不会监听数据
export default MyComponent;
4.2.3 设置路由
// .src/router.js
import React, { PropTypes } from 'react';
import { Router, Route } from 'dva/router';
import Users from './routes/Users';
export default function({ history }) {
return (
<Router history={history}>
<Route path="/users" component={Users} />
</Router>
);
};
容器组件雏形
// .src/routes/Users.jsx
import React, { PropTypes } from 'react';
function Users() {
return (
<div>User Router Component</div>
);
}
export default Users;
4.2.4 设计容器组件
自顶向下的设计方法:先设计容器组件,再逐步细化内部的展示容器
组件的定义方式
// 方法一: es6 的写法,当组件设计 react 生命周期时,可采用这种写法
// 具有生命周期的组件,可以在接收到传入数据变化时,自定义执行方法,有自己的行为模式
// 比如在组件生成后调用 xx 请求(componentDidMount)、可以自己决定要不要更新渲染(shouldComponentUpdate) 等
class App extends React.Component({});
// 方法二: stateless 的写法,定义无状态组件
// 无状态组件,仅仅根据传入的数据更新,修改自己的渲染内容
const App = (props) => ({});
容器组件:
// ./src/routes/Users.jsx
import React, { Component, PropTypes } from 'react';
// 引入展示组件 (暂时都没实现)
import UserList from '../components/Users/UserList';
import UserSearch from '../components/Users/UserSearch';
import UserModal from '../components/Users/UserModal';
// 引入 css 样式表
import styles from './style.less'
function Users() {
// 向 userListProps 中传入静态数据
const userSearchProps = {};
const userListProps = {
total: 3,
current: 1,
loading: false,
dataSource: [
{
name: '张三',
age: 23,
address: '成都',
},
{
name: '李四',
age: 24,
address: '杭州',
},
{
name: '王五',
age: 25,
address: '上海',
},
],
};
const userModalProps = {};
return (
<div className={styles.normal}>
{/* 用户筛选搜索框 */}
<UserSearch {...userSearchProps} />
{/* 用户信息展示列表 */}
<UserList {...userListProps} />
{/* 添加用户 & 修改用户弹出的浮层 */}
<UserModal {...userModalProps} />
</div>
);
}
// 很关键的对外输出 export;使外部可通过 import 引用使用此组件
export default Users;
展示组件 UserList
// ./src/components/Users/UserList.jsx
import React, { Component, PropTypes } from 'react';
// 采用 antd 的 UI 组件
import { Table, message, Popconfirm } from 'antd';
// 采用 stateless 的写法
const UserList = ({
total,
current,
loading,
dataSource,
}) => {
const columns = [{
title: '姓名',
dataIndex: 'name',
key: 'name',
render: (text) => <a href="#">{text}</a>,
}, {
title: '年龄',
dataIndex: 'age',
key: 'age',
}, {
title: '住址',
dataIndex: 'address',
key: 'address',
}, {
title: '操作',
key: 'operation',
render: (text, record) => (
<p>
<a onClick={()=>{}}>编辑</a>
<Popconfirm title="确定要删除吗?" onConfirm={()=>{}}>
<a>删除</a>
</Popconfirm>
</p>
),
}];
// 定义分页对象
const pagination = {
total,
current,
pageSize: 10,
onChange: ()=>{},
};
// 此处的 Table 标签使用了 antd 组件,传入的参数格式是由 antd 组件库本身决定的
// 此外还需要在 index.js 中引入 antd import 'antd/dist/antd.css'
return (
<div>
<Table
columns={columns}
dataSource={dataSource}
loading={loading}
rowKey={record => record.id}
pagination={pagination}
/>
</div>
);
}
export default UserList;
4.3 添加 Reducer
在整个应用中,只有 model
中的 reducer
函数可以直接修改自己所在 model
的 state
参数,其余都是非法操作;
并且必须使用 return {...state}
的形式进行修改
4.3.1 第一步:实现 reducer 函数
// models/users.js
// 使用静态数据返回,把 userList 中的静态数据移到此处
// querySuccess 这个 action 的作用在于,修改了 model 的数据
export default {
namespace: 'users',
state: {},
subscriptions: {},
effects: {},
reducers: {
querySuccess(state){
const mock = {
total: 3,
current: 1,
loading: false,
list: [
{
id: 1,
name: '张三',
age: 23,
address: '成都',
},
{
id: 2,
name: '李四',
age: 24,
address: '杭州',
},
{
id: 3,
name: '王五',
age: 25,
address: '上海',
},
]
};
// return 的内容是一个对象,涵盖原 state 中的所有属性,以实现“更新替换”的效果
return {...state, ...mock, loading: false};
}
}
}
4.3.2 第二步:关联 Model 中的数据源
// routes/Users.jsx
import React, { PropTypes } from 'react';
// 最后用到了 connect 函数,需要在头部预先引入 connect
import { connect } from 'dva';
function Users({ location, dispatch, users }) {
const {
loading, list, total, current,
currentItem, modalVisible, modalType
} = users;
const userSearchProps={};
// 使用传入的数据源,进行数据渲染
const userListProps={
dataSource: list,
total,
loading,
current,
};
const userModalProps={};
return (
<div className={styles.normal}>
{/* 用户筛选搜索框 */}
<UserSearch {...userSearchProps} />
{/* 用户信息展示列表 */}
<UserList {...userListProps} />
{/* 添加用户 & 修改用户弹出的浮层 */}
<UserModal {...userModalProps} />
</div>
);
}
// 声明组件的 props 类型
Users.propTypes = {
users: PropTypes.object,
};
// 指定订阅数据,并且关联到 users 中
function mapStateToProps({ users }) {
return {users};
}
// 建立数据关联关系
export default connect(mapStateToProps)(Users);
4.3.3 第三步:通过发起 Action,在组件中获取 Model 中的数据
// models/users.js
// 在组件生成后发出 action,示例:
componentDidMount() {
this.props.dispatch({
type: 'model/action', // type 对应 action 的名字
});
}
// 在本次实践中,在访问/users/路由时,就是我们获取用户数据的时机
// 因此把 dispatch 移至 subscription 中
// subcription,订阅(或是监听) 一个数据源,然后根据条件 dispatch 对应的 action
// 数据源可以是当前的时间、服务器的 websocket 连接、keyboard 输入、geolocation 变化、history 路由变化等等
// 此处订阅的数据源就是路由信息,当路由为/users,则派发'querySuccess'这个 effects 方法
subscriptions: {
setup({ dispatch, history }) {
history.listen(location => {
if (location.pathname === '/users') {
dispatch({
type: 'querySuccess',
payload: {}
});
}
});
},
},
4.3.4 第四步: 在 index.js 中添加 models
// model 必须在此完成注册,才能全局有效
// index.js
app.model(require('./models/users.js'));
4.4 添加 Effects
Effects
的作用在于处理异步函数,控制数据流程。
因为在真实场景中,数据都来自服务器,需要在发起异步请求获得返回值后再设置数据,更新 state
。
因此我们往往在 Effects
中调用 reducer
export default {
namespace: 'users',
state: {},
subscriptions: {},
effects: {
// 添加 effects 函数
// call 与 put 是 dva 的函数
// call 调用执行一个函数
// put 则是 dispatch 执行一个 action
// select 用于访问其他 model
*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
}
});
}
},
},
reducers: {}
}
// 添加请求处理 包含了一个 ajax 请求
// models/users.js
import request from '../utils/request';
import qs from 'qs';
async function query(params) {
return request(`/api/users?${qs.stringify(params)}`);
}
4.5 把请求处理分离到 service 中
用意在于分离(可复用的)ajax 请求
// services/users.js
import request from '../utils/request';
import qs from 'qs';
export async function query(params) {
return request(`/api/users?${qs.stringify(params)}`);
}
// 在 models 中引用
// models/users.js
import {query} from '../services/users';
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论