最近对自己工作上的项目和业余时间写的项目总结了一下。有挺多感受,在此记录下学习到的东西和我的better practice。在这里我将代码分成三层:通用模块层,适配层和展示层。在通用模块的基础之上构建出适配层,由适配层处理复杂多变的业务需求,适配层灵活且多变。在技术上不断沉淀基础的通用模块,这不仅不会成为工程师们额外的开发负担,反而可以提升业务需求的实现效率,提高系统的健壮性和可持续发展。
项目结构
东西比较多,这里分条陈述。首先是项目结构。为什么先说项目结构呢? 因为从项目结构可以看出项目的组织方式,直接可以大体看出项目的思想和“架构”。直接贴出个人比较喜欢的代码组织结构。
├── /mock/ # 数据mock的接口文件 ├── /src/ # 项目源码目录 │ ├── /components/ # 项目组件 │ ├── /routes/ # 路由组件(页面维度) │ ├── /models/ # 数据模型 │ ├── /services/ # 数据接口 │ ├── /utils/ # 工具函数 │ ├── route.js # 路由配置 │ ├── index.js # 入口文件 │ ├── index.less │ └── index.html ├── package.json # 项目信息 └── proxy.config.js # 数据mock配置
这里是参考支付宝团队的开发结构。支付宝前端团队从自己封装的一个叫roof框架(D2前端大会提到过),到后来使用非常火热的redux,再到现在的dva。我是一直在关注。他的代码组织结构我不喜欢的地方两点:一是effect(可以简单的理解为异步代码调用的地方)写法,他们是用es6 generator 以同步的方式写异步,去解决 side effects, 而我更喜欢 es6 的promise 和 es7的 async。另一个就是关于mock,我不喜欢在本地mock数据,我更倾向于面向接口编程,即前后端制定好接口,然后服务器自动根据接口mock数据。
业务实体
之前做的项目里面直接去除了这一层。 所有的业务逻辑都在展示曾上。所有的数据监测都在service。 这样做在项目简单的时候似乎问题不大,但是项目趋于复杂,这种弊端就凸显出来了。所以建议还是做业务实体。这里的实体是充血实体。而不是JAVA中VO(贫血实体)。 业务实体比较简单,就是一个对象。对象中字段和方法。 字段用于绑定业务实体的组件,方法用于更新字段
import fetch from '../utils/fetch.js';
import R from 'ramda';
// xxCheck结尾代表的是是否勾选了xx
let config = {
isMultiCheck: false,
}
// invoke while rendered
// be sure 'init' invoked only once
function fetchUserConfig() {
fetch('xxxxxxxxxx')
.then(userConfig => {
config = userConfig;
})
}
// Object -> Object
function setUserConfig(userConfig) {
Object.keys(config).map(key => {
// preCheck
if (R.isNil(userConfig[key])) return -1;
config[key] = userConfig[key];
return 0;
})
}
// String -> (bool | object | string)
function getUserConfig() {
return config;
}
module.exports = {
getUserConfig,
setUserConfig,
}
异步处理
如果没有异步,我们可以比较容易的写出纯函数。 大多数的副作用都来自于异步。所以处理好异步是项目(尤其是大项目)面临的一个大考验。对于同步代码,直接放到reducer中就可以了。所以异步的代码可以放到一个地方去,一方面集中方便管理,另一方面耦合度降低。
异步的代码放到service中
export async function query() {
return request('/api/funds');
}
react项目的组件划分
组件尽量分为展示型和容器型。 展示型个人倾向于写成stateless component。 容器型主要是监听数据行为(绑定业务实体),然后将数据通过prop传递给子组件。
容器型:
import React, { Component, PropTypes } from 'react';
// connect 方法可以将组件和数据关联在一起
import { connect } from 'redux';
// 组件本身
const MyComponent = (props)=>{};
MyComponent.propTypes = {};
// 监听属性,建立组件和数据的映射关系
function mapStateToProps(state) {
return {...state.data};
}
// 关联 model
export default connect(mapStateToProps)(MyComponent);
展示型:
import React, { Component, PropTypes } from 'react';
// 需要的数据通过 Container Component 通过 props 传递下来
const MyComponent = (props)=>{}
MyComponent.propTypes = {};
// 不监听数据
export default MyComponent;
对组件分类,主要有两个好处:
- 让项目的数据处理更加集中;
- 让组件高内聚低耦合,更加聚焦
项目初始化
项目初始化工具对于个人来讲就是简化重复工作,对于团队来讲就是强行代码统一。
之前用的初始化工具是ant-init,这个初始化工具弊端很明显,就是强行绑定了antd框架。后来antd团队改造了一下,出了一个叫dva-cli的 cli工具,可以一键初始化代码。但是这个工具的弊端就是没有可配置项,就是直接生成,没有用户参与。更好的体验是用户可以在命令行自定义一些配置。我觉得可以用commander 解析用户输入做一些自定义的初始化操作。 比如
状态管理库:redux
异步处理方式:async
和
状态管理库:refux
异步处理方式:generator
会生成两种项目结构和示例程序。
只要在命令行敲一行命令,项目就初始化好了。下图是初始化好的项目目录结构