前端代码规范

198 阅读18分钟

前端代码规范

1.1 编程规约

命名规范

项目命名

全部采用小写方式,以中线分隔。

mall-managemenet-system

目录命名

全部采用小写方式, 以中划线分隔,有复数结构时,要采用复数命名法, 缩写不用复数。

scripts/styles/components/images/utils/layouts/demo-styles/demo-scripts/img/doc

JS、CSS、SCSS、HTML、PNG 文件命名

全部采用小写方式, 以中划线分隔。

render-dom.js

1.1.4 命名严谨性

代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的 英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用

fujian/rmb/xiamen 等国际通用名称,可视同英文

杜绝完全不规范的缩写,避免望文不知义:

AbstractClass ==> Absclass;
condition ==> condi

1.2 HTML 规范

HTML 类型

  • 使用 HTML5 DOCTYPE,在 HTML 文档的开头使用 <!DOCTYPE html> 来声明文档的 HTML 版本。
  • 指定 html 标签上的 lang 属性,以指出文档的语言。这有助于读屏、翻译等工具的工作。
  • 页面需要指定 title 标签,有且仅有 1 个。
  • 使用 UTF-8 字符编码。声明一个明确的字符编码,可以让浏览器更快速高效地确定适合网页内容的渲染方式。
<!DOCTYPE html>
<html lang="zh-CN">
  <head> 
      <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> 
    <meta charset="UTF-8" /> 
    <title>Page title</title> 
  </head>
  <body> 
   <img src="images/company-logo.png" alt="Company">
 </body> 
  </html>

缩进

统一使用 2 个空格缩进,不要使用 4 个空格或 tab 缩进。

分块注释

在每一个块状元素,列表元素和表格元素后,加上一对 HTML 注释。

标签

  • 标签名统一使用小写。
  • HTML5 中新增很多语义化标签,所以优先使用语义化标签,避免一个页面都是 div 或者 p 标 签。
<header></header> 
<footer></footer>

<title>:页面主体内容

<hn>:h1 ~ h6,分级标题, <hn><title> 协调有利于搜索引擎优化

<ul>:无序列表

<ol>:有序列表

<header>:页眉通常包括网站标志、主导航、全站链接以及搜索框。

<nav>:标记导航,仅对文档中重要的链接群使用。

<main>:页面主要内容,一个页面只能使用一次。如果是web应用,则包围其主要功能。

<article>:定义外部的内容,其中的内容独立于文档的其余部分。

<section>:定义文档中的节(section、区段)。比如章节、页眉、页脚或文档中的其他部分。

<aside>:定义其所处内容之外的内容。如侧栏、文章的一组链接、广告、友情链接、相关产品列表等。

<footer>:页脚,只有当父级是body时,才是整个页面的页脚。

<small>:呈现小号字体效果,指定细则,输入免责声明、注解、署名、版权。

<strong>:和 em 标签一样,用于强调文本,但它强调的程度更强一些。

<em>:将其中的文本表示为强调的内容,表现为斜体。

<mark>:使用黄色突出显示部分文本。

<figure>:规定独立的流内容(图像、图表、照片、代码等等)(默认有40px左右margin)。

<figcaption>:定义 figure 元素的标题,应该被置于 figure 元素的第一个或最后一个子元素的位置。

<cite>:表示所包含的文本对某个参考文献的引用,比如书籍或者杂志的标题。

<blockquoto>:定义块引用,块引用拥有它们自己的空间。

<q>:短的引述(跨浏览器问题,尽量避免使用)。

<time>:datetime属性遵循特定格式,如果忽略此属性,文本内容必须是合法的日期或者时间格式。

<abbr>:简称或缩写。

<dfn>:定义术语元素,与定义必须紧挨着,可以在描述列表dl元素中使用。

<address>:作者、相关人士或组织的联系信息(电子邮件地址、指向联系信息页的链接)。

<del>:移除的内容。

<ins>:添加的内容。

<code>:标记代码。

<meter>:定义已知范围或分数值内的标量测量。(Internet Explorer 不支持 meter 标签)

<progress>:定义运行中的进度(进程)。

属性

  • 使用双引号(" ") 而不是单引号(’ ') 。
  • 不要为 Boolean 属性添加取值。XHTML 需要每个属性声明取值,但是 HTML5 并不需要。一个元素中 Boolean 属性存在即表示取值 true,不存在则表示取值 false
  • 自定义属性的命名:以 data- 为前缀
<!-- bad -->
<link rel='stylesheet' href='example.css'><!-- good -->
<link rel="stylesheet" href="example.css" /><!-- bad -->
<input type="text" disabled="disabled" />
<input type="checkbox" value="1" checked="checked" />
<select>
  <option value="1" selected="selected">1</option>
</select><!-- good -->
<input type="text" disabled />
<input type="checkbox" value="1" checked />
<select>
  <option value="1" selected>1</option>
</select><!-- bad -->
<a modal="toggle" href="#">
  Example link
</a><!-- good -->
<a data-modal="toggle" href="#">
  Example link
</a>

资源加载

引入 CSS 和 JavaScript 时无需指定 type。 根据 HTML5 规范,引入 CSS 和 JavaScript 时通常不需要指明 type,因为 text/csstext/javascript 分别是他们的默认值。

<!-- bad -->
<link type="text/css" rel="stylesheet" href="example.css" />
<style type="text/css">
  /* ... */
</style>
<script type="text/javascript" src="example.js"></script>

<!-- good -->
<link rel="stylesheet" href="example.css" />
<style>
  /* ... */
</style>
<script src="example.js"></script>

在 head 标签内引入 CSS,在 body 结束标签前引入 JS。在 <body></body> 中指定外部样式表和嵌入式样式块可能会导致页面的重排和重绘,对页面的渲染造成影响。因此,一般情况下,CSS 应在 <head></head> 标签里引入。除了基础库等必须要在 DOM 加载之前运行的 JavaScript 脚本,其他都在靠近 body 结束标签前引入,以防止出现页面渲染的阻塞

<!-- bad -->
<!DOCTYPE html>
<html>
  <head>
    <script src="mod-a.js"></script>
    <script src="jquery.js"></script>
  </head>
  <body>
    <style>
      .mod-example {
        padding-left: 15px;
      }
    </style>
  </body>
</html>

<!-- good -->
<!DOCTYPE html>
<html>
  <head>
    <style>
      .mod-example {
        padding-left: 15px;
      }
    </style>
  </head>
  <body>
    ...
    <script src="path/to/my/script.js"></script>
  </body>
</html>
  • 外部资源的引用地址跟随页面协议,省略协议部分。
  • 使用 preload 预加载关键资源
  • 使用 dns-prefetch 和 preconnect 处理 DNS 解析延迟问题,提高网页加载性能
<link rel="stylesheet" href="//g.alicdn.com/lib/style/index-min.css" />

<link rel="preload" href="style.css" as="style" />
<link rel="preload" href="main.js" as="script" />

<link rel="preconnect" href="https://fonts.googleapis.com/" crossorigin />
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />

1.3 css规范

命名

  • 类名使用小写字母,以中划线分隔
  • id使用驼峰命名
  • scss中的变量,函数,混合,placeholder采用驼峰命名

ID和class的名称总是使用可以反应元素目的和用途的名称,或者通用的名称,代替表象和晦涩难懂的名称

// bad
.fw-800 {
    font-weight: 800;
 }
 .red {
   color: red; 
  }

// good
.heavy {
   font-weight: 800;
 }
.important { 
  color: red; 
 }

选择器

  • css 选择器中避免使用标签名,从结构,表现,行为分离的原则来看,应该尽量避免css中出现HTML标签,并且在css选择其中存在潜在的问题
  • 使用直接子选择器很多前端开发人员写选择器链的使用不使用 直接子选择器 。有时候,这可能会导致疼痛的设计问题并且有时候会很耗性能,然而,在任何情况下,这是一个非常不好的做法,需要匹配到DOM末端,你应该总是考虑直接子选择器
// bad
.content .title {
   font-size: 2rem;
  }
 
 // good
 .content > .title {
   font-size: 2rem;
 }

尽量使用缩写属性

// bad
border-top-style: none; 
font-family: palatino, georgia, serif; 
font-size: 100%; line-height: 1.6; 
padding-bottom: 2em; 
padding-left: 1em;
padding-right: 1em; 
padding-top: 0;

// good
border-top: 0; 
font: 100%/1.6 palatino, georgia, serif; 
padding: 0 1em 2em;

省略 0 后面的单位

 // bad
 div {
     padding-bottom: 0px; 
     margin: 0em;
 }
 
 // good
 div {
    padding-bottom: 0; 
    margin: 0; 
}

避免使用 ID 选择器及全局标签选择器防止污染全局样式

// bad
#header {
 padding-bottom: 0px; 
 margin: 0em;
}

// good
.header { 
    padding-bottom: 0px; 
    margin: 0em; 
}

LESS/SCSS 规范

代码组织

  • 将公共 less 文件放置在 style/less/common 文件夹,例: // color.less,common.less
  • 按以下顺序组织
    1. @import
    2. 变量声明
    3. 样式声明
@import "mixins/size.less"; 
@default-text-color: #333; 
.page {
 width: 960px; 
 margin: 0 auto; 
}

避免嵌套层级过多

将嵌套深度限制在3级,对于大于4级的嵌套,给予重新评估。这可以避免出现过于详实的css选择器。比打量的嵌套规则。但可读性收到影响时,将之打断。推荐避免出现多余20行嵌套规则出现。

// bad
.main {
   .title { 
      .name { 
           color: #fff;  
         } 
     }
}
// good
.main-title {
   .name { color: #fff; }
 }

Javascript 规范

命名

1) 采用小写驼峰命名 lowerCamelCase,代码中的命名均不能以下划线, 也不能以下划线或美元符号结束

反例: name / name / name$

2) 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风 格,必须遵从驼峰形式

正例: localValue / getHttpMessage() / inputUserId

其中 method 方法命名必须是 动词 或者 动词+名词 形式

正例: saveShopCarData /openShopCarInfoDialog

反例: save / open / show / go

特此说明,增删查改,详情统一使用如下 5 个单词,不得使用其他(目的是为了统一各个端)

add / delete / update / detail / get 
附: 函数方法常用的动词: 
get 获取/set 设置, 
add 增加/remove 删除, 
create 创建/destory 销毁, 
start 启动/stop 停止, 
open 打开/close 关闭, 
read 读取/write 写入, 
load 载入/save 保存,
begin 开始/end 结束, 
backup 备份/restore 恢复,
import 导入/export 导出, 
split 分割/merge 合并,
inject 注入/extract 提取,
attach 附着/detach 脱离, 
bind 绑定/separate 分离, 
view 查看/browse 浏览, 
edit 编辑/modify 修改,
select 选取/mark 标记, 
copy 复制/paste 粘贴,
undo 撤销/redo 重做, 
insert 插入/delete 移除,
add 加入/append 添加, 
clean 清理/clear 清除,
index 索引/sort 排序,
find 查找/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 聚集
3) 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚, 不要嫌名字长

正例: MAX_STOCK_COUNT

反例: MAX_COUNT

使用有意义的变量代替数组下标
👎 
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\]+[,\\s]+(.+?)\s*(\d{5})?$/;
saveCityZipCode(
  address.match(cityZipCodeRegex)[1],
  address.match(cityZipCodeRegex)[2]
);

👍
const address = "One Infinite Loop, Cupertino 95014";
const cityZipCodeRegex = /^[^,\]+[,\\s]+(.+?)\s*(\d{5})?$/;
const [_, city, zipCode] = address.match(cityZipCodeRegex) || [];
saveCityZipCode(city, zipCode);
变量名要简洁,不要附加无用信息
👎 
const Car = {
  carMake: "Honda",
  carModel: "Accord",
  carColor: "Blue"
};
function paintCar(car, color) {
  car.carColor = color;
}

👍
const Car = {
  make: "Honda",
  model: "Accord",
  color: "Blue"
};
function paintCar(car, color) {
  car.color = color;
}
6) 消除魔术字符串
👎 
setTimeout(blastOff, 86400000);

👍 
const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;
setTimeout(blastOff, MILLISECONDS_PER_DAY);
7) 使用默认参数替代短路运算符
👎
function createMicrobrewery(name) {
  const breweryName = name || "Hipster Brew Co.";
  // ...
}

👍 
function createMicrobrewery(name = "Hipster Brew Co.") {
  // ...
}

代码格式

使用 2 个空格进行缩进

正例:

if (x < y) {
 x += 10;
  } else {
   x += 1; 
}
不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以 提升可读性

说明:任何情形,没有必要插入多个空行进行隔开。

字符串

统一使用单引号(‘),不使用双引号(“)。这在创建 HTML 字符串非常有好处:

正例:

   let str = 'foo';
   let testDiv = '<div id="test"></div>'; 

反例:

let str = 'foo'; 
let testDiv = "<div id='test'></div>";

对象声明

使用字面值创建对象

正例: let user = {};

反例: let user = new Object();

使用字面量来代替对象构造器

正例: var user = { age: 0, name: 1, city: 3 };

反例:

var user = new Object(); 
user.age = 0; 
user.name = 0; 
user.city = 0; 

使用 ES6+

必须优先使用ES6+中新增的语法糖和函数。这将简化你的程序,并让你的代码更加灵活和可复用

,比如箭头函数,await/async,解构,let,for..of..等等。

undefined 判断

永远不要直接使用 undefined 进行变量判断;使用 typeof 和字符串’undefined’对变量进行判断。

正例:

 if (typeof person === 'undefined') { ... }

反例:

if (person === undefined) { ... }

条件判断和循环最多三层

条件判断能使用三目运算符和逻辑运算符解决的,就不要使用条件判断,但是谨记不要写太长的三目运算符。如果超过三层就必须抽成函数,并写清楚注释。

this 的转换命名

上下文this的引用只能使用self来命名

慎用 console.log

因 console.log大量使用会有性能问题。所以代码git push之前必须清理个人调试使用的console

函数

一个函数只做一件事的好处在于易于理解、易于测试
👎
function emailClients(clients) {
  clients.forEach(client => {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}

👍 
function emailActiveClients(clients) {
  clients.filter(isActiveClient).forEach(email);
}
function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

---------------------分割线-----------------------

👎
function createFile(name, temp) {
  if (temp) {
    fs.create(`./temp/${name}`);
  } else {
    fs.create(name);
  }
}

👍 
function createFile(name) {
  fs.create(name);
}
function createTempFile(name) {
  createFile(`./temp/${name}`);
}
函数参数不多于2个,如果有很多参数就利用object传递,并使用解构

推荐使用解构的几个原因:

  1. 看到函数签名可以立即了解有哪些参数
  2. 解构能克隆传递到函数中的参数对象的值(浅克隆),有助于防止副作用.
  3. linter可以提示有哪些参数未被使用

👎
function createMenu(title, body, buttonText, cancellable) {
  // ...
}
createMenu("Foo", "Bar", "Baz", true);

👍 
function createMenu({ title, body, buttonText, cancellable }) {
  // ...
}
createMenu({
  title: "Foo",
  body: "Bar",
  buttonText: "Baz",
  cancellable: true
});
边界或者错误优先处理,尽量保持结构的扁平
👎
function(data) {
  if(data) {
    console.log(data)
  } else {
    console.log(data)
  }
}


👍 
function(data) {
  if(!data) {
  	console.log(data)
   	return
  } 
  
  console.log(data)
}

1.6 注释

单行注释

[强制] 必须独占一行。// 后跟一个空格,缩进与下一行被注释说明的代码一致。

多行注释

[建议] 避免使用 /*...*/ 这样的多行注释。有多行注释内容时,使用多个单行注释。

文档化注释

[强制] 为了便于代码阅读和自文档化,以下内容必须包含以 /**...*/ 形式的块注释中。

解释:

  1. 文件
  2. namespace
  3. 函数或方法
  4. 类属性
  5. 事件
  6. 全局变量
  7. 常量

[强制] 文档注释前必须空一行。

[建议] 自文档化的文档说明 what,而不是 how。

类型定义

[强制] 类型定义都是以 { 开始, 以 } 结束。

解释:

常用类型如:{string}, {number}, {boolean}, {Object}, {Function}, {RegExp}, {Array}, {Date}。

类型不仅局限于内置的类型,也可以是自定义的类型。比如定义了一个类 Developer,就可以使用它来定义一个参数和返回值的类型。

[强制] 对于基本类型 {string}, {number}, {boolean},首字母必须小写。

类型定义语法示例解释
String{string}--
Number{number}--
Boolean{boolean}--
Object{Object}--
Function{Function}--
RegExp{RegExp}--
Array{Array}--
Date{Date}--
单一类型集合{Array.}string 类型的数组
多类型{(number|boolean)}可能是 number 类型, 也可能是 boolean 类型
允许为null{?number}可能是 number, 也可能是 null
不允许为null{!Object}Object 类型, 但不是 null
Function类型{function(number, boolean)}函数, 形参类型
Function带返回值{function(number, boolean):string}函数, 形参, 返回值类型
PromisePromise.<resolveType, rejectType>Promise,成功返回的数据类型,失败返回的错误类型
参数可选@param {string=} name可选参数, =为类型后缀
可变参数@param {...number} args变长参数, ...为类型前缀
任意类型{*}任意类型
可选任意类型@param {*=} name可选参数,类型不限
可变任意类型@param {...*} args变长参数,类型不限

文件注释

[强制] 文件顶部必须包含文件注释,用 @file 标识文件说明。

示例:

/**
 * @file Describe the file
 */

[建议] 文件注释中可以用 @author 标识开发者信息。

解释:

开发者信息能够体现开发人员对文件的贡献,并且能够让遇到问题或希望了解相关信息的人找到维护人。通常情况文件在被创建时标识的是创建者。随着项目的进展,越来越多的人加入,参与这个文件的开发,新的作者应该被加入 @author 标识。

@author 标识具有多人时,原则是按照 责任 进行排序。通常的说就是如果有问题,就是找第一个人应该比找第二个人有效。比如文件的创建者由于各种原因,模块移交给了其他人或其他团队,后来因为新增需求,其他人在新增代码时,添加 @author 标识应该把自己的名字添加在创建人的前面。

@author 中的名字不允许被删除。任何劳动成果都应该被尊重。

业务项目中,一个文件可能被多人频繁修改,并且每个人的维护时间都可能不会很长,不建议为文件增加 @author 标识。通过版本控制系统追踪变更,按业务逻辑单元确定模块的维护责任人,通过文档与wiki跟踪和查询,是更好的责任管理方式。

对于业务逻辑无关的技术型基础项目,特别是开源的公共项目,应使用 @author 标识。

示例:

/**
 * @file Describe the file
 * @author author-name(mail-name@domain.com)
 *         author-name2(mail-name2@domain.com)
 */

命名空间注释

[建议] 命名空间使用 @namespace 标识。

示例:

/**
 * @namespace
 */
var util = {};

类注释

[建议] 使用 @class 标记类或构造函数。

解释:

对于使用对象 constructor 属性来定义的构造函数,可以使用 @constructor 来标记。

示例:

/**
 * 描述
 *
 * @class
 */
function Developer() {
    // constructor body
}

[建议] 使用 @extends 标记类的继承信息。

示例:

/**
 * 描述
 *
 * @class
 * @extends Developer
 */
function Fronteer() {
    Developer.call(this);
    // constructor body
}
util.inherits(Fronteer, Developer);

[强制] 使用包装方式扩展类成员时, 必须通过 @lends 进行重新指向。

解释:

没有 @lends 标记将无法为该类生成包含扩展类成员的文档。

示例:

/**
 * 类描述
 *
 * @class
 * @extends Developer
 */
function Fronteer() {
    Developer.call(this);
    // constructor body
}
​
util.extend(
    Fronteer.prototype,
    /** @lends Fronteer.prototype */{
        getLevel: function () {
            // TODO
        }
    }
);

[强制] 类的属性或方法等成员信息不是 public 的,应使用 @protected@private 标识可访问性。

解释:

生成的文档中将有可访问性的标记,避免用户直接使用非 public 的属性或方法。

示例:

/**
 * 类描述
 *
 * @class
 * @extends Developer
 */
var Fronteer = function () {
    Developer.call(this);
​
    /**
     * 属性描述
     *
     * @type {string}
     * @private
     */
    this.level = 'T12';
​
    // constructor body
};
util.inherits(Fronteer, Developer);
​
/**
 * 方法描述
 *
 * @private
 * @return {string} 返回值描述
 */
Fronteer.prototype.getLevel = function () {
};

函数/方法注释

[强制] 函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标识。

解释:

return 关键字仅作退出函数/方法使用时,无须对返回值作注释标识。

[强制] 参数和返回值注释必须包含类型信息,且不允许省略参数的说明。

[建议] 当函数是内部函数,外部不可访问时,可以使用 @inner 标识。

示例:

/**
 * 函数描述
 *
 * @param {string} p1 参数1的说明
 * @param {string} p2 参数2的说明,比较长
 *     那就换行了.
 * @param {number=} p3 参数3的说明(可选)
 * @return {Object} 返回值描述
 */
function foo(p1, p2, p3) {
    var p3 = p3 || 10;
    return {
        p1: p1,
        p2: p2,
        p3: p3
    };
}

[强制] 对 Object 中各项的描述, 必须使用 @param 标识。

示例:

/**
 * 函数描述
 *
 * @param {Object} option 参数描述
 * @param {string} option.url option项描述
 * @param {string=} option.method option项描述,可选参数
 */
function foo(option) {
    // TODO
}

[建议] 重写父类方法时, 应当添加 @override 标识。如果重写的形参个数、类型、顺序和返回值类型均未发生变化,可省略 @param@return,仅用 @override 标识,否则仍应作完整注释。

解释:

简而言之,当子类重写的方法能直接套用父类的方法注释时可省略对参数与返回值的注释。

事件注释

[强制] 必须使用 @event 标识事件,事件参数的标识与方法描述的参数标识相同。

示例:

/**
 * 值变更时触发
 *
 * @event Select#change
 * @param {Object} e e描述
 * @param {string} e.before before描述
 * @param {string} e.after after描述
 */
this.fire(
    'change',
    {
        before: 'foo',
        after: 'bar'
    }
);

[强制] 在会广播事件的函数前使用 @fires 标识广播的事件,在广播事件代码前使用 @event 标识事件。

[建议] 对于事件对象的注释,使用 @param 标识,生成文档时可读性更好。

示例:

/**
 * 点击处理
 *
 * @fires Select#change
 * @private
 */
Select.prototype.clickHandler = function () {

    /**
     * 值变更时触发
     *
     * @event Select#change
     * @param {Object} e e描述
     * @param {string} e.before before描述
     * @param {string} e.after after描述
     */
    this.fire(
        'change',
        {
            before: 'foo',
            after: 'bar'
        }
    );
};

常量注释

[强制] 常量必须使用 @const 标记,并包含说明和类型信息。

示例:

/**
 * 常量说明
 *
 * @const
 * @type {string}
 */
var REQUEST_URL = 'myurl.do';

复杂类型注释

[建议] 对于类型未定义的复杂结构的注释,可以使用 @typedef 标识来定义。

示例:

// `namespaceA~` 可以换成其它 namepaths 前缀,目的是为了生成文档中能显示 `@typedef` 定义的类型和链接。
/**
 * 服务器
 *
 * @typedef {Object} namespaceA~Server
 * @property {string} host 主机
 * @property {number} port 端口
 */

/**
 * 服务器列表
 *
 * @type {Array.<namespaceA~Server>}
 */
var servers = [
    {
        host: '1.2.3.4',
        port: 8080
    },
    {
        host: '1.2.3.5',
        port: 8081
    }
];

细节注释

对于内部实现、不容易理解的逻辑说明、摘要信息等,我们可能需要编写细节注释。

[建议] 细节注释遵循单行注释的格式。说明必须换行时,每行是一个单行注释的起始。

示例:

function foo(p1, p2, opt_p3) {
    // 这里对具体内部逻辑进行说明
    // 说明太长需要换行
    for (...) {
        ....
    }
}

[强制] 有时我们会使用一些特殊标记进行说明。特殊标记必须使用单行注释的形式。下面列举了一些常用标记:

解释:

  1. TODO: 有功能待实现。此时需要对将要实现的功能进行简单说明。
  2. FIXME: 该处代码运行没问题,但可能由于时间赶或者其他原因,需要修正。此时需要对如何修正进行简单说明。
  3. HACK: 为修正某些问题而写的不太好或者使用了某些诡异手段的代码。此时需要对思路或诡异手段进行描述。
  4. XXX: 该处存在陷阱。此时需要对陷阱进行描述。

1.4 Vue 编码基础

组件规范

组件名为多个单词。

组件名应该始终是多个单词组成(大于等于 2)。

这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的

export default {
  name: 'todo-item'
  // ...
};
组件文件名为 pascal-case 格式
components/
|- my-component.vue
基础组件文件名为 base 开头,使用完整单词而不是缩写。

正例:

components/
|- base-button.vue
|- base-table.vue
|- base-icon.vue

反例:

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
和父组件紧密耦合的子组件应该以父组件名作为前缀命名

正例:

components/
|- todo-list.vue
|- todo-list-item.vue
|- todo-list-item-button.vue
|- user-profile-options.vue (完整单词)

反例:

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
|- UProfOpts.vue (使用了缩写)
在 Template 模版中使用组件,应使用 PascalCase 模式,并且使用自闭合组件。

正例:

<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent />
<Row><table :column="data"/></Row>

反例:

<my-component /> <row><table :column="data"/></row>
Prop 定义应该尽量详细

必须使用 camelCase 驼峰命名

必须指定类型

必须加上注释,表明其含义

必须加上 required 或者 default,两者二选其一

如果有业务需要,必须加上 validator 验证

正例:

defineProps({
  prop: {
    type: String,
    required: true,
    default: 100,
		validator(value) {
      return ['success', 'warning', 'danger'].includes(value)
    }
  }
})
为组件样式设置作用域

正例:

<!-- 使用 `scoped` 特性 -->
<style scoped>
  .btn-close {
    background-color: red;
  }
</style>
组件必须要用一个根结点

正例:

<template>
	<div></div>
</template>

模板中使用简单的表达式

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

const listCount = computed(() => {});

指令都使用缩写形式

指令推荐都使用缩写形式,(用 : 表示 v-bind: 、用 @ 表示 v-on: 和用 # 表示 v-slot:)

正例:

<input
  @input="onInput"
  @focus="onFocus"
>

标签顺序保持一致

单文件组件应该总是让标签顺序保持为

正例:

<template>...</template>
<script>...</script>
<style>...</style>

反例:

<template>...</template>
<style>...</style>
<script>...</script>

必须为 v-for 设置键值 key

v-show 与 v-if 选择

如果运行时,需要非常频繁地切换,使用 v-show ;如果在运行时,条件很少改变,使用 v-if。

Vue Router 规范

页面跳转数据传递使用路由参数

页面跳转,例如 A 页面跳转到 B 页面,需要将 A 页面的数据传递到 B 页面,推荐使用 路由参数进行传参

正例:

let id = ' 123';
router.push({ name: 'userCenter', query: { id: id } });
使用路由懒加载(延迟加载)机制
{
    path: '/uploadAttachment',
    name: 'uploadAttachment',
    meta: {
      title: '上传附件'
    },
    component: () => import('@/view/components/uploadAttachment/index.vue')
  },

** router 中的命名规范**

path、childrenPoints 命名规范采用kebab-case命名规范(尽量vue文件的目录结构保持一致,因为目录、文件名都是kebab-case,这样很方便找到对应的文件)

name 命名规范采用KebabCase命名规范且和component组件名保持一致!(因为要保持keep-alive特性,keep-alive按照component的name进行缓存,所以两者必须高度保持一致)

// 动态加载
export const reload = [
  {
    path: '/reload',
    name: 'reload',
    component: Main,
    meta: {
      title: '动态加载',
      icon: 'icon iconfont'
    },
    children: [
      {
        path: '/reload/smart-reload-list',
        name: 'SmartReloadList',
        meta: {
          title: 'SmartReload',
          childrenPoints: [
            {
              title: '查询',
              name: 'smart-reload-search'
            },
            {
              title: '执行reload',
              name: 'smart-reload-update'
            },
            {
              title: '查看执行结果',
              name: 'smart-reload-result'
            }
          ]
        },
        component: () =>
          import('@/views/reload/smart-reload/smart-reload-list.vue')
      }
    ]
  }
];
router 中的 path 命名规范

path除了采用kebab-case命名规范以外,必须以 / 开头,即使是children里的path也要以 / 开头。如下示例

目的:

经常有这样的场景:某个页面有问题,要立刻找到这个vue文件,如果不用以/开头,path为parent和children组成的,可能经常需要在router文件里搜索多次才能找到,而如果以/开头,则能立刻搜索到对应的组件

{
    path: '/file',
    name: 'File',
    component: Main,
    meta: {
      title: '文件服务',
      icon: 'ios-cloud-upload'
    },
    children: [
      {
        path: '/file/file-list',
        name: 'FileList',
        component: () => import('@/views/file/file-list.vue')
      },
      {
        path: '/file/file-add',
        name: 'FileAdd',
        component: () => import('@/views/file/file-add.vue')
      },
      {
        path: '/file/file-update',
        name: 'FileUpdate',
        component: () => import('@/views/file/file-update.vue')
      }
    ]
  }

自定义hooks 规范

导入元素必须使用对象导出,不能使用数组

// bad
const [, , , , , , isSameCompany] = useUserInfo();

// good
const { isSameCompany } = useUserInfo();

三、开发总结

数据增删改查

数据更新成功后,前端直接判断更新接口是否成功,成功了直接修改数据,不能重新刷新接口获取最新数据