title | date | tags | ||
---|---|---|---|---|
开发文档 |
2018-03-28 04:44:25 -0700 |
|
[TOC]
.
├── dist/ // 构建产物目录
└── src/ // 源码目录,可选,把里面的内容直接移到外面即可
├── constants/ // 常量文件
├── Images.js // 图片资源管理
└── TopicName.js // 服务相关配置
├── layouts/
│ └── index.js // 全局布局
├── pages/ // 页面目录,里面的文件即路由
├── user/ // 用户中心页面
├── components/ // 组件分化
├── Item.js // 组件 Item
└── Item.less // 组件 Item 样式
├── models/ // redux 相关
├── services/ // 服务层
├── page.js
└── page.less
├── document.ejs // HTML 模板
├── 404.js // 404 页面
└── page1.js // 页面 1,任意命名
├── global.less // 约定的全局样式文件,自动引入
├── test/ // 测试用例放这里
├── .umirc.js // umi 配置
├── .webpackrc // webpack 配置
└── package.json
每个单页都需要在 pages
文件夹下新建一个对应的文件夹,该文件夹名会自动作为路由名,如上 pages/user
文件夹,文件夹下新建一个 page.js
(强制要求以 page.js
命名) 作为页面入口,
该页中用到的组件需要放到 components
文件夹内,如 pages/user/components
,
redux 等状态管理放到 models
文件夹中,如 pages/user/models
,
服务请求放到 services
文件夹中,如 pages/user/services
,
页面(组件)的样式文件需与页面(组件)文件同目录且同名,样式文件的后缀名统一采用 .less
组件不涉及视图刷新操作的统一采用 React.PureComponent
,不了解 PureComponent
的暂时使用 Component
代替,避免一些不必要的麻烦,
组件文件名和组件命名统一采用大写字母开头的驼峰式命名法
图片资源统一在 constants/Images.js
中管理,在引用时引入,如下:
// 引入
import images from '../constants/Images';
...
// 使用
<img src={ images.DEFAULT_AVATAR } />
在页面文件中引用 connect
,如下:
import { connect } from 'dva';
class UserPage extends React.Component {
}
const mapStateToProps = (state) => ({
})
export default connect(mapStateToProps)(UserPage);
在 page 对应的目录下新建 models
文件夹,在 models
文件夹内新建一文件,如 pages/user/models/user.js
,内容如下:
export default {
namespace: 'user', // 针对页面取名,会作为全局 state 中的 key 值
state: {
// 页面所需 state
},
reducers: {
// 改变的 state 的函数
},
effects: {
// 网络请求及数据处理函数
},
subscriptions: {
}
}
然后在页面通过 connect
的特性能获取到对应的 state
// 此处 state 为整个应用的全局 state(仅针对 models 中的 state,页面内的 state 不在计算内)
const mapStateToProps = (state) => ({
user: state.user, // 获取 user 模块的 state
})
在页面目录下新建 services
目录,新建一个文件,如:pages/user/services/user.js
/**
* 网络请求
* @param {Object} socket Socket 连接(必需)
* @param {Object} params 服务所需参数
*/
export function reqUserInfo(socket, params) {
// 固定写法
socket.sendToServer('MessageType', 'MessageName', { ...params })
}
在需要改变 state 的时候需要发送一个 Action,需要获取 socket,如:pages/user/page.js
class UserPage extends React.Component {
// 需要 socket 时必需声明
static contextTypes = {
socket: PropTypes.object.isRequired,
}
componentDidMount() {
const { dispatch } = this.props; // connect 之后默认带有 dispatch
const { socket } = this.context; // 使用 socket 做网络请求
dispatch({
type: 'user/reqUserInfo', // 格式为: models.namespace + reducers/effects 中函数名
payload: { // 传递数据
socket, // 网络请求必传 socket
params: {
userId: 123
}
}
})
}
}
在 models 中定义 reqUserInfo
函数,函数名与 page 中发送的 type
函数同名,如 pages/user/models/user.js
// 引入对应的 service 服务文件
import * as services from '../services/user.js';
export default {
namespace: 'user', // 针对页面取名,会作为全局 state 中的 key 值
state: {
// 页面所需 state
},
reducers: {
// 改变的 state 的函数
},
effects: {
// 网络请求及数据处理函数,此函数名与页面中发的 Action 的 type 函数名一致
*reqUserInfo({ payload }, { call, put }) {
// 调用 service 服务并传递参数
yield call(services.reqUserInfo, payload.socket, { userId: payload.params.userId })
}
},
subscriptions: {
}
}
在页面中接收服务返回需要引用已封装好的 Event
组件,如 pages/user/page.js
import Event from '../../socket/Event';
class UserPage extends React.Component {
/**
* 接收服务返回
* @param {Object} response 服务返回结果
*/
handleResponse = (response) => {
// 处理服务返回结果
// 我们统一将服务返回发送到 models 中处理
const { dispatch } = this.props;
dispatch({
type: 'user/resUserInfo',
payload: { response } // 处理数据不需要 socket
})
}
render() {
return (
<div>
{/* event 属性为固定值 'svevent',handler 为接收服务返回的回调 */}
<Event event="svevent" handler={this.handleResponse} />
</div>
)
}
}
在 models 中处理服务返回数据,如 pages/user/models/user.js
import { Toast } from 'antd-mobile';
export default {
namespace: 'user', // 针对页面取名,会作为全局 state 中的 key 值
state: {
// 页面所需 state
userInfo: {},
},
reducers: {
// 改变的 state 的函数
userInfoSuccessed(state, {payload}) {
// 返回新的数据
return {...state, userInfo: payload.userInfo}
}
},
effects: {
// 网络请求及数据处理函数
*resUserInfo({ payload }, { call, put }) {
// 判断是否成功调用服务,可能存在不同接口返回的状态不一样
if(payload.response.state === 1) {
// 成功返回,改变 state 值,渲染视图,改变 state 统一在 reducer 中处理
yield put({
type: 'userInfoSuccessed', // 在models 文件中发送 Action 不需要 models.namespace
payload: { userInfo: payload.data }
})
} else {
// 错误返回,提示错误信息
Toast.info(response.message)
}
}
},
subscriptions: {
}
}
得到服务返回的数据后在页面中显示,如 pages/user/page.js
class UserPage extends React.Component {
render() {
// 获取信息
const { userInfo } = this.props;
return (
<div>
{/* 将信息显示在前端页面 */}
<div>用户名:{ userInfo.username }</div>
</div>
)
}
}
/**
* 将 redux 中的 state 取出作为 page 的 props
* @param {Object} state 应用整体 state
*/
const mapStateToProps = (state) => ({
userInfo: state.user.userInfo, // 得到对应 models 中的 state,格式为 state[models.namespace][key]
})
export default connect(mapStateToProps)(UserPage);