1.项目规范
1.1 项目命名
格式:模块关键词-web/h5/app/mo
// Don't ❌
umiMo
// Do ✅
UMI-MO
1.2 项目结构
├── .vscode # vscode编码格式
├── config # 配置
├── doc # 前端规范文档
├── public # 项目公共资源
│ └── favicon.ico
├── src
│ ├── api # 后台接口服务
│ │ └── http.ts # http封装
│ │ └── urls.ts # 接口url路径
│ ├── assets # 本地静态资源
│ ├── components # 业务通用组件
│ ├── directive # 自定义指令
│ ├── hooks # hooks钩子
│ ├── layouts # 通用布局
│ ├── locales # 国际化资源
│ ├── pages # 业务页面入口和常用模板
│ ├── router # route路由
│ ├── store # 全局状态管理
│ ├── utils # 工具库
│ ├── locales # 国际化资源
├── .eslintrc.js # eslint配置文件
├── .prettierrc.js # prettier配置文件
├── README.md
└── package.json
1.3 目录命名
全部采用驼峰方式,有复数结构时,要采用复数命名法, 缩写不用复数。
// Don't ❌
demoStyles/imgs/docs/company-user
// Do ✅
components/utils/pages/companyUser
1.4 命名严谨性
代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用
// Don't ❌
DaZhePromotion [打折] / getPingfenByName() [评分] / int 某变量 = 3
// Do ✅
supplier / henan / luoyang / rmb 等国际通用的名称,可视同英文
杜绝完全不规范的缩写,避免望文不知义: 反例:AbstractClass “缩写”命名成 AbsClass;condition “缩写”命名成 condi,此类随意缩写严重降低了代码的可阅读性。
2.JS 规范
2.1 命名
方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
// Don't ❌
_name / name_ / name$
// Do ✅
localValue / getHttpMessage() / inputUserId
注:其中 method 方法命名必须是 动词 或者 动词+名词 形式
// Don't ❌
save / open / show / go
// Do ✅
saveShopCarData /openShopCarInfoDialog
附: 函数方法常用的动词
| get | 获取 | set | 设置 | add | 增加 | remove | 删除 |
|---|---|---|---|---|---|---|---|
| create | 创建 | destory | 销毁 | start | 启动 | stop | 停止 |
| open | 打开 | close | 关闭 | read | 读取 | write | 写入 |
| load | 载入 | save | 保存 | begin | 开始 | end | 结束 |
| backup | 备份 | restore | 恢复 | import | 导入 | export | 导出 |
| split | 分割 | merge | 合并 | attach | 附着 | detach | 脱离 |
| inject | 注入 | extract | 提取 | bind | 绑定 | separate | 分离 |
| view | 查看 | browse | 浏览 | edit | 编辑 | modify | 修改 |
| select | 选取 | mark | 标记 | copy | 复制 | paste | 粘贴 |
| undo | 撤销 | redo | 重做 | insert | 插入 | delete | 移除 |
| append | 添加 | clean | 清理 | clear | 清除 | search | 搜索 |
| increase | 增加 | decrease | 减少 | play | 播放 | pause | 暂停 |
| launch | 启动 | run | 运行 | compile | 编译 | execute | 执行 |
| debug | 调试 | trace | 跟踪 | observe | 观察 | listen | 监听 |
| build | 构建 | publish | 发布 | input | 输入 | output | 输出 |
| encode | 编码 | decode | 解码 | encrypt | 加密 | decrypt | 解密 |
| compress | 压缩 | decompress | 解压缩 | pack | 打包 | unpack | 解包 |
| parse | 解析 | emit | 生成 | connect | 连接 | disconnect | 断开 |
| send | 发送 | receive | 接收 | download | 下载 | upload | 上传 |
| refresh | 刷新 | synchronize | 同步 | update | 更新 | revert | 复原 |
| lock | 锁定 | unlock | 解锁 | check out | 签出 | check in | 签入 |
| submit | 提交 | commit | 交付 | push | 推 | pull | 拉 |
| expand | 展开 | collapse | 折叠 | enter | 进入 | exit | 退出 |
| abort | 放弃 | quit | 离开 | obsolete | 废弃 | depreciate | 废旧 |
| collect | 收集 | aggregate | 聚集 | index | 索引 | sort | 排序 |
| find | 查找 |
2.2 变量
2.2.1 使用有意义的名称
// Don't ❌
const foo = 'JDoe@example.com'
const bar = 'John'
const age = 23
const qux = true
// Do ✅
const email = 'John@example.com'
const firstName = 'John'
const age = 23
const isActive = true
布尔变量通常需要回答特定问题,例如
isActive
didSubscribe
hasLinkedAccount
2.2.2 避免添加不必要的上下文
当对象或类已经包含了上下文的命名时,不要再向变量名称添加冗余的上下文。
// Don't ❌
const user = {
userId: '296e2589-7b33-400a-b762-007b730c8e6d',
userEmail: 'JDoe@example.com',
userFirstName: 'John',
userLastName: 'Doe',
userAge: 23,
}
user.userId
// Do ✅
const user = {
id: '296e2589-7b33-400a-b762-007b730c8e6d',
email: 'JDoe@example.com',
firstName: 'John',
lastName: 'Doe',
age: 23,
}
user.id
2.2.3 避免硬编码值
确保声明有意义且可搜索的常量,而不是直接插入一个常量值。全局常量可以采用 SCREAMING_SNAKE_CASE 风格命名
// Don't ❌
setTimeout(clearSessionData, 900000)
// Do ✅
const SESSION_DURATION_MS = 15 * 60 * 1000
setTimeout(clearSessionData, SESSION_DURATION_MS)
2.3 函数
2.3.1 使用有意义的名称
动词、动词+名词
// Don't ❌
function toggle() {
// ...
}
function agreed(user) {
// ...
}
// Do ✅
function toggleThemeSwitcher() {
// ...
}
function didAgreeToAllTerms(user) {
// ...
}
2.3.2 使用默认参数
默认参数比 && || 或在函数体内使用额外的条件语句更干净。
// Don't ❌
function createMicrobrewery(name) {
const breweryName = name || 'Hipster Brew Co.'
// ...
}
// Do ✅
function createMicrobrewery(name = 'Hipster Brew Co.') {
// ...
}
2.3.3 限制参数的数量
如果参数超过两个,使用 ES2015/ES6 的解构语法,不用考虑参数的顺序。
// Don't ❌
function sendPushNotification(title, message, image, isSilent, delayMs) {
// ...
}
sendPushNotification('New Message', '...', 'http://...', false, 1000)
// Do ✅
function sendPushNotification({ title, message, image, isSilent, delayMs }) {
// ...
}
const notificationConfig = {
title: 'New Message',
message: '...',
image: 'http://...',
isSilent: false,
delayMs: 1000,
}
sendPushNotification(notificationConfig)
2.3.4 避免在一个函数中做太多事情
一个函数应该一次做一件事,这有助于减少函数的大小和复杂性,使测试、调试和重构更容易。
// Don't ❌
function emailClients(clients) {
clients.forEach((client) = >{
const clientRecord = database.lookup(client);
if (clientRecord.isActive()) {
email(client);
}
});
}
// Do ✅
function emailActiveClients(clients) {
clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
const clientRecord = database.lookup(client);
return clientRecord.isActive();
}
2.3.5 避免使用布尔标志作为参数
通过布尔标志的 true 或 false,来判断执行逻辑,违反了一个函数干一件事的原则。
// Don't ❌
function createFile(name, isPublic) {
if (isPublic) {
fs.create(`./public/${name}`)
} else {
fs.create(name)
}
}
// Do ✅
function createFile(name) {
fs.create(name)
}
function createPublicFile(name) {
createFile(`./public/${name}`)
}
2.3.6 避免写重复的代码
// Don't ❌
function renderCarsList(cars) {
cars.forEach((car) => {
const price = car.getPrice()
const make = car.getMake()
const brand = car.getBrand()
const nbOfDoors = car.getNbOfDoors()
render({ price, make, brand, nbOfDoors })
})
}
function renderMotorcyclesList(motorcycles) {
motorcycles.forEach((motorcycle) => {
const price = motorcycle.getPrice()
const make = motorcycle.getMake()
const brand = motorcycle.getBrand()
const seatHeight = motorcycle.getSeatHeight()
render({ price, make, brand, nbOfDoors })
})
}
// Do ✅
function renderVehiclesList(vehicles) {
vehicles.forEach((vehicle) => {
const price = vehicle.getPrice()
const make = vehicle.getMake()
const brand = vehicle.getBrand()
const data = { price, make, brand }
switch (vehicle.type) {
case 'car':
data.nbOfDoors = vehicle.getNbOfDoors()
break
case 'motorcycle':
data.seatHeight = vehicle.getSeatHeight()
break
}
render(data)
})
}
2.3.7 避免副作用
函数接收一个值返回一个新值,除此之外的行为我们都称之为副作用,比如修改全局变量、对文件进行 IO 操作等。当函数确实需要副作用时,比如对文件进行 IO 操作时,请不要用多个函数/类进行文件操作,有且仅用一个函数/类来处理。也就是说副作用需要在唯一的地方处理。
副作用的三大天坑:
- 随意修改可变数据类型
- 随意分享没有数据结构的状态
- 没有在统一地方处理副作用
// Don't ❌
// 全局变量被一个函数引用
// 现在这个变量从字符串变成了数组,如果有其他的函数引用,会发生无法预见的错误。
var name = 'Ryan McDermott'
function splitIntoFirstAndLastName() {
name = name.split(' ')
}
splitIntoFirstAndLastName()
console.log(name) // ['Ryan', 'McDermott'];
// Do ✅
var name = 'Ryan McDermott'
var newName = splitIntoFirstAndLastName(name)
function splitIntoFirstAndLastName(name) {
return name.split(' ')
}
console.log(name) // 'Ryan McDermott';
console.log(newName) // ['Ryan', 'McDermott'];
另外,如果你将一个可变值传递给函数,你应该直接克隆一个新值返回,而不是直接改变该它。
// Don't ❌
const addItemToCart = (cart, item) = >{
cart.push({
item,
date: Date.now()
});
};
// Do ✅
const addItemToCart = (cart, item) = >{
return [...cart, {
item,
date: Date.now()
}]
};
2.3.8 不要写全局方法
在 JavaScript 中,永远不要污染全局,会在生产环境中产生难以预料的 bug。举个例子,比如你在 Array.prototype 上新增一个 diff 方法来判断两个数组的不同。而你同事也打算做类似的事情,不过他的 diff 方法是用来判断两个数组首位元素的不同。很明显你们方法会产生冲突,遇到这类问题我们可以用 ES2015/ES6 的语法来对 Array 进行扩展。
// Don't ❌
Array.prototype.diff = function diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem = >!hash.has(elem));
};
// Do ✅
class SuperArray extends Array {
diff(comparisonArray) {
const hash = new Set(comparisonArray);
return this.filter(elem = >!hash.has(elem));
}
}
2.3.9 删除弃用代码
很多时候有些代码已经没有用了,但担心以后会用,舍不得删。如果你忘了这件事,这些代码就永远存在那里了。大胆删吧,你可以在代码库历史版本中找到它。
2.4 错误处理
// Don't ❌
try {
functionThatMightThrow();
} catch(error) {
console.log(error);
}
getdata().then((data) = >{
functionThatMightThrow(data);
}).
catch((error) = >{
console.log(error);
});
// Do ✅
try {
functionThatMightThrow();
} catch(error) {
// 这一种选择,比起 console.log 更直观
console.error(error);
// 也可以在界面上提醒用户
notifyUserOfError(error);
// 也可以把异常传回服务器
reportErrorToService(error);
// 其他的自定义方法
}
getdata().then((data) = >{
functionThatMightThrow(data);
}).
catch((error) = >{
// 这一种选择,比起 console.log 更直观
console.error(error);
// 也可以在界面上提醒用户
notifyUserOfError(error);
// 也可以把异常传回服务器
reportErrorToService(error);
// 其他的自定义方法
});
2.5 测试
自测重要且必要!!!
2.6 代码注释
代码注释不是越多越好,在必要的场景下(业务逻辑复杂,API 参数特殊,公用组件等)需要合理的注释,便于后续维护。
单行注释
// 单行注释
多行注释
/**
* 多行注释
*/
函数 & 组件注释 建议采用JSDoc 的方式
// Don't ❌
const UploadFile = (
defaultValue,
maxSize,
maxLength,
className,
disabled,
accept,
callback
) = >{...}
// Do ✅
/**
* @param { Array } defaultValue // 默认文件list
* @param { Number } maxSize // 文件最大尺寸,单位 MB
* @param { Number } maxLength // 文件最大数量
* @param { String } className // 类名
* @param { Boolean } disabled // 是否不可编辑
* @param { String } accept // 支持上传的文件类型
* @param { Function } callback // 回调函数
*/
const UploadFile = ({
defaultValue = {},
maxSize = 2,
maxLength = 5,
className,
disabled = false,
accept,
callback
}) = >{...}
使用 JSDoc 后在组件或函数调用的地方鼠标 Hover 时可以提示所需的参数及类型,能极大提升开发效率和开发幸福度。
3.CSS 规范
3.1 文件命名
// Don't ❌
MyScript.js
my-camel-case-name.css
my_type.html
my-file-min.css
// Do ✅
myScript.js
myCamelCaseName.css
myType.js
myFile.min.css
一般资源文件的命名规则:
- 以小写字母开头,避免数字开头
- 用驼峰写法,这样的话不容易在引用的时候因为大小写而出错
- 对于压缩过的文件,比如
css或者js文件,使用.min代替-min
3.2 ID 和 Class 的命名规范
ID 和 Class 的主要习惯于如下命名方式:
- 全部字母用小写,避免使用驼峰命名法。
- 使用短横线-来作为连接单词之间的字符,避免使用下划线_。
.post-title {
font-size: 20px;
color: #41b883;
}
- 命名尽可能语义化,让人一目了然。
// Don't ❌
fw-800 {
font-weight: 800;
}
.red {
color: red;
}
// Do ✅
.heavy {
font-weight: 800;
}
.important {
color: red;
}
3.3 尽可能避免使用 ID 选择器
// Don't ❌
#article p {
line-height: 28px;
}
// Do ✅
.article p {
line-height: 28px;
}
3.4 避免使用标签进行双重限定
// Don't ❌
p.desc {
color: #666;
}
// Do ✅
.desc {
color: #666;
}
3.5 尽可能的精确,但是最好不要超过 3 级
// Don't ❌
.content .title {
font-size: 2rem;
}
// Do ✅
.content > .content-body > .title {
font-size: 2rem;
}
3.6 尽可能的使用简写属性
// Don't ❌
.box {
margin: 0;
margin-top: 10px;
}
// Do ✅
.box {
margin: 10px 0 0;
}
3.7 双引号
font-family属性,如果属性值是带空格的英文比如Helvetica Neue或者是中文,那么建议加上双引号,比如content属性URI资源的引用,有使用到url()引入资源的时候,不用带引号。比如引入背景图片、字体定义的时候引入字体包等
.tip:before {
content: '!';
font-family: Dosis, 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif;
background: url(../img/tip.png) no-repeat center;
}
3.8 尽量不要使用 !important
// Don't ❌
.heavy {
font-weight: 700 !important;
}
// Do ✅
.heavy p,
.heavy a {
font-weight: 700;
}
4.HTML 规范
4.1 缩进
缩进使用 2 个空格(一个 tab),嵌套的节点应该缩进。
4.2 分块注释
在每一个块状元素,列表元素和表格元素后,加上一对 HTML 注释。
<!-- 产品展示列表 -->
<ul>
<li>Product A</li>
<li>Product B</li>
<li>Product C</li>
<li>Product D</li>
<li>Product E</li>
</ul>
4.3 语义化标签
HTML5 中新增很多语义化标签,所以优先使用语义化标签,避免一个页面都是 div 或者 p 标签。
// Don't ❌
<div>
<p>Header</p>
<p>Footer</p>
</div>
// Do ✅
<header>Header</header>
<footer>Footer</footer>
4.4 必要的 class
在关键位置增加 class,能方便后续维护时快速定位,特别是在 React 中采用 styled-components 后,如果不在组件外层增加 class,要想在众多代码中快速找到异常页面的代码绝非易事。
import styled from 'styled-components'
// Don't ❌
<ReviewContainer>
...
</ReviewContainer>
// Do ✅
<ReviewContainer className="review-container">
...
</ReviewContainer>
5.分支管理规范
一套好的分支管理流程, 会有助于团队的协同开发,提高项目工程化水平。
- master:生产环境的稳定分支
-
- 只存线上的代码,只有确定可以上线时的才合并到master上,并且在master的基础上打Tag,如:0.1,0.2 或 0.3。
- staging:演示分支
-
- 从sit或uat分支合并过来,要经过代码QC后,方可合并,以提高代码质量。合并发布到staging分支后,并发审核邮件让产品最终确认。
- sit/uat:测试分支
-
- 功能开发完成后,发布给测试人员测试的分支。
- dev:开发分支
-
- 初次创建develop时,需要从master分支拉取,保持开发时代码和线上最新的代码相同。develop分支是在开发时的最终分支,具有所有当前版本需要上线的所有功能。
- feature:特定开发分支
-
- 用于开发功能的分支,必须从最新的develop分支代码拉取。分支命名参考feature/20210117_xxxxx(和功能相关的名字)。
6.git commit 日志
一个好的提交信息, 会有助于提高项目的整体质量。
- why
-
- 格式统一的提交信息可以帮助自动化生成 changelog
- 版本库不只是存放代码的仓库, 也记录了项目的开发过程。这些记录应该可以帮助后来者快速地学习和回顾代码。也能方便其他协作者 review 你的代码
- 原则: 半年后, 你能看懂你的 commit 做了什么东西
- 必要信息
-
- 解决了什么问题?
- 问题是什么导致的?(简短说明使用什么方式, 策略, 修复了问题)
- 变化可能影响哪些地方(一个提交不应该做超过 2 个功能的变动)
- 格式:type + body
-
- type:说明提交类型,方便阅读者快速区分提交的类型。
-
-
- feat: 新增 feature
- develop: 功能开发
- style: 仅仅修改了空格、格式缩进、代码格式、样式等
- fix: 修复了 bug
- docs: 文档
- refactor: 代码重构。代码重构不涉及新功能和 bug 修复,不应该影响原有功能, 包括对外暴露的接口
- test: 增加测试
- chore: 构建过程, 辅助工具升级。如升级依赖, 升级构建工具
- perf: 性能优化
- revert: 回滚到上一个版本
- release: 构建或发布版本
- safe: 修复安全问题
- versiontest: 版本提测
-
-
- body:对本次提交的详细描述。 如果变动很简单, 可以省略
// Don't ❌
update
fix
// Do ✅
style: 优化 Sales RFQ 详情页布局,提升用户体验;影响 Sales RFQ 详情页
fix: MOLD-666 MO SKU编辑页面点击preview跳转显示404异常
7.发版流程规范
- 同时合 staging, prod 两个代码分支后发审核邮件
- 邮件批示后发 staging, 等 staging 验收
- staing 验收确认后,非特殊情况,19:30 后发 prod
- 需紧急发版的在审批邮件申请时说明,验收后直接上生产