中台系统 前端编码规范

184 阅读9分钟

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交付pushpull
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()
  });
};

// Doconst 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 的命名规范

IDClass 的主要习惯于如下命名方式:

  • 全部字母用小写,避免使用驼峰命名法。
  • 使用短横线-来作为连接单词之间的字符,避免使用下划线_。
.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


// Dostyle: 优化 Sales RFQ 详情页布局,提升用户体验;影响 Sales RFQ 详情页
fix: MOLD-666 MO SKU编辑页面点击preview跳转显示404异常

7.发版流程规范

  • 同时合 staging, prod 两个代码分支后发审核邮件
  • 邮件批示后发 staging, 等 staging 验收
  • staing 验收确认后,非特殊情况,19:30 后发 prod
  • 需紧急发版的在审批邮件申请时说明,验收后直接上生产