前端规范
一、 命名规则
“计算机科学只存在两个难题:缓存失效和命名。” ——Phil KarIton
代码和文件命名要严谨,禁止使用中文和英文混合的方式,更不允许直接用汉字的方式命名,无法用英文表达的可以使用拼音代替(如weibo、wuhan),代替的时候不要用缩写;英文单词不确定的可以多查翻译软件,不要有低级的拼写错误。
强烈推荐安装 Code Spell Checker 插件,使用方式参考第十四章
1. 项目命名
1、全部小写的方式,中划线分割;
2、项目名称规范(有则写无则不写):scrm/tcloud(事业部)-car/retail/qw(业务线名称)-wit/royal/gwm(项目名称)-mc/oc(微服务)-web/h5/applet/iOS/Android/app(客户端)
//❌bad
scrm_web_chery-gf
scrmWebCheryGf
ScrmWebCheryGf
//✔good
scrm-cherygf-web
scrm-royal-web
注意:1、微服务名称可有可无;
2、项目名称私有化的时候才有;
3、项目中统一用中划线禁止用下划线命名(自动化变量k8s容器名称不识别)前端Gitlab-CICD&SonarQube使用;
4、项目名称不要使用数字命名;例如:71360等;
2. 目录命名
文件夹的命名统一用小写,保证项目有良好的可移植性,可跨平台 (windows/mac/linux)运行没有问题,文件内文件的命名按照组件的命名规范来执行相关参考
基于同一的命名规范,引用也需要注意大小写问题
项目目录规范:
|
├── build // 构建相关 ├── config // 配置相关 ├── src // 源代码 │ ├── api // 所有请求 │ ├── assets // 主题 字体等静态资源 │ ├── components // 全局公用组件 │ ├── directive // 全局指令 │ ├── filtres // 全局 filter │ ├── icons // 项目所有 svg icons │ ├── lang // 国际化 language │ ├── mock // 项目mock 模拟数据 │ ├── router // 路由 │ ├── store // 全局 store管理 │ ├── styles // 全局样式 │ ├── utils // 全局公用方法 │ ├── vendor // 公用vendor │ ├── views // view │ ├── App.vue // 入口页面 │ ├── main.js // 入口 加载组件 初始化等 │ └── permission.js // 权限管理 ├── public // 第三方不打包资源 │ └── Tinymce // 富文本 │ └── favicon.ico // favicon图标 │ └── index.html // 入口html ├── .babelrc // babel-loader 配置 ├── eslintrc.js // eslint 配置项 ├── .gitignore // git 忽略项 └── package.json // package.json |
3. 文件命名
3.1. js、ts、css、scss、html、png文件命名
全部采用小写的方式,以中划线分割
//❌bad
tableMixin.js/CustomerSOP.html
//✔good
table-mixin.js/global-color.scss/index.html/company-brand.png
3.2. vue文件
3.2.1. 基础组件名
应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如 Base、App 或 V。
//❌bad
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue
//✔good
components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue
3.2.2. 单例组件名
只应该拥有单个活跃实例的组件应该以 The 前缀命名,以示其唯一性。
这不意味着组件只可用于一个单页面,而是每个页面只使用一次。这些组件永远不接受任何 prop,因为它们是为你的应用定制的,而不是它们在你的应用中的上下文。如果你发现有必要添加 prop,那就表明这实际上是一个可复用的组件,只是目前在每个页面里只使用一次。
//❌bad
components/
|- Heading.vue
|- MySidebar.vue
//✔good
components/
|- TheHeading.vue
|- TheSidebar.vue
3.2.3. 紧密耦合的组件名
和父组件紧密耦合的子组件应该以父组件名作为前缀命名。
如果一个组件只在某个父组件的场景下有意义,这层关系应该体现在其名字上。因为编辑器通常会按字母顺序组织文件,所以这样做可以把相关联的文件排在一起。
//❌bad
components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue
//✔good
components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue
components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue
3.2.4. 组件名中的单词顺序
组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾。
//❌bad
components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue
//✔good
components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue
3.2.5. 模板中的组件名大小写
总是 PascalCase 的
//❌bad
<!-- 在单文件组件和字符串模板中 -->
<mycomponent/>
<!-- 在单文件组件和字符串模板中 -->
<myComponent/>
//✔good
<!-- 在单文件组件和字符串模板中 -->
<MyComponent/>
3.2.6. 完整单词的组件名
组件名应该倾向于完整单词而不是缩写。
//❌bad
components/
|- SdSettings.vue
|- UProfOpts.vue
//✔good
omponents/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue
4. 参数和属性命名
4.1. js/ts和vue命名
4.1.1. 变量
命名方式:小驼峰式命名法.
命名规范:前缀名词
命名建议:语义化
eslint规范:prefer-const、no-var
//❌bad
var a = 1
var b = 2
//✔good
const a = 1
const b = 2
//❌bad
var count = 1
if (count < 10) {
count += 1
}
//✔good
let count = 1
if (count < 10) {
count += 1
}
4.1.2. 常量
命名方式:全部大写
命名规范:使用大写字母和下划线来组合命名,下划线用以分割单词
命名建议:语义化
const MAX_COUNT = 10;
const URL = ‘http://www.foreverz.com'
4.1.3. 函数
命名方式:小驼峰式命名法。
命名规范:前缀应当为动词。
命名建议:语义化
可以参考如下的动作
eslint规则:camelcase
代码起名常用动词及其含义
|
动词 |
含义 |
返回值 |
|
can |
判断是否可执行某个动作(权限) |
函数返回一个布尔值。true:可执行;false:不可执行 |
|
has |
判断是否含有某个值 |
函数返回一个布尔值。true:含有此值;false:不含有此值 |
|
is |
判断是否为某个值 |
函数返回一个布尔值。true:为某个值;false:不为某个值 |
|
get |
获取某个值 |
函数返回一个非布尔值 |
|
set |
设置某个值 |
无返回值、返回是否设置成功或者返回链式对象 |
|
load/save |
加载/保存某些数据 |
无返回值或者返回是否加载完成的结果 |
|
handle |
处理某些数据 |
无返回值或者返回是否处理完成的结果 |
|
add update delete remove |
操作数据 |
|
|
create / destory |
创建 / 销毁 |
|
|
start / stop |
启动 / 停止 |
|
|
open / close |
打开 / 关闭 |
|
|
read / write |
读取 / 写入 |
|
|
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 |
插入 / 移除 |
|
|
append / remove |
加入 / 移除 |
|
|
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 |
连接 / 断开 |
|
|
subscribe /unsubscribe |
订阅 / 取消订阅 |
|
|
send / receive |
发送 / 接收 |
|
|
download / upload |
下载 / 上传 |
|
|
refresh /synchronize |
刷新 / 同步 |
|
|
update / revert |
更新 / 复原 |
|
|
lock / unlock |
锁定 / 解锁 |
|
|
checkOut / checkIn |
签出 / 签入 |
|
|
submit / commit |
提交 / 交付 |
|
|
push / pull |
推 / 拉 |
|
|
expand / collapse |
展开 / 折叠 |
|
|
enter / exit |
进入 / 退出 |
|
|
abort / quit |
放弃 / 离开 |
|
|
collect / aggregate |
收集 / 聚集 |
// 是否可阅读
function canRead() {
return true;
}
// 获取名称
function getName() {
return this.name;
}
4.1.4. 类和构造函数
命名方式:大驼峰式命名法,首字母大写,且不允许有返回值
命名规范:前缀为名称。
命名建议:语义化
eslint规则:no-constructor-return、new-cap
class Person {
public name: string;
constructor(name) {
this.name = name;
}
}
const person = new Person('mevyn');
公共属性和方法:跟变量和函数的命名一样。
私有属性和方法:前缀为_(下划线),后面跟公共属性和方法一样的命名方式。
class Person {
private _name: string;
constructor() { }
// 公共方法
getName() {
return this._name;
}
// 公共方法
setName(name) {
this._name = name;
}
}
const person = new Person();
person.setName('mervyn');
person.getName(); // ->mervyn
4.2. css命名
(1)class命名使用BEM其实是块(block)、元素(element)、修饰符(modifier)的缩写,利用不同的区块,功能以及样式来给元素命名。这三个部分使用__与--连接(这里用两个而不是一个是为了留下用于块儿的命名)。
命名约定的模式如下:
.block{}
.block__element{}
.block--modifier{}
block 代表了更高级别的抽象或组件
block__element 代表 block 的后代,用于形成一个完整的 block 的整体
block--modifier代表 block 的不同状态或不同版本
为什么使用BEM?
4.2.1. 性能
CSS引擎查找样式表,对每条规则都按从右到左的顺序去匹配
以下这段代码看起来很快,实际上很慢。
通常我们会认为浏览器是这样工作的:找到唯一ID元素ul-id —> 把样式应用到li元素上。
事实上: 从右到左进行匹配,遍历页面上每个li元素并确定其父元素
#ul-id li {}
所以不要让你的css超过三层
4.2.2. 语义化
看以下例子是否一目了然。
.person{} /*人*/
.person__hand{} /*人的手*/
.person--female{} /*女人*/
.person--female__hand{} /*女人的手*/
.person__hand--left{} /*人的左手*/
如果写成以下这样,你将不知道这是hand指的是手还是指针,female是女性还是雌性,female-hand是女性还是雌性的手?left-hand什么的左手,还是指针的左边?
.person{}
.hand{}
.female{}
.female-hand{}
.left-hand{}
在scss中如何使用?
使用@at-root内联选择器模式,编译出来的CSS无任何嵌套(这是关键)
.person {
@at-root #{&}__hand {
color: red;
@at-root #{&}--left {
color: yellow;
}
}
@at-root #{&}--female {
color: blue;
@at-root #{&}__hand {
color: green;
}
}
}
/*生成的css*/
.person__hand {
color: red;
}
.person__hand--left {
color: yellow;
}
.person--female{
color: blue;
}
.person--female__hand {
color: green;
}
(2)id一般参与样式,命名的话使用驼峰,如果是给js调用钩子就需要设置为js_xxxx的方
二、 JS开发规范
1. 对象创建/使用规范
1.1. 使用字面量值创建对象
eslint规范:no-object-constructor
//❌bad
const obj = new Object()
//✔good
const obj = {}
1.2. 禁止使用保留字作为对象的键值
原因:在 IE8 下不会运行
eslint规则:no-restricted-properties
//❌bad
const a = {
default: {}, // default 是保留字
common: {}
}
//✔good
const a = {
defaults: {},
common: {}
}
1.3. 动态属性名创建对象
当使用动态属性名创建对象时,请使用对象计算属性名来进行创建
原因:因为这样做就可以让你在一个地方定义所有的对象属性
function getKey(k) {
return `a key named ${k}`
}
//❌bad
const obj = {
id: 5,
name: 'San Francisco'
};
obj[getKey('enabled')] = true
//✔good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true
};
1.4. 对象方法简写
//❌bad
const item = {
value: 1,
addValue: function (val) {
return item.value + val
}
}
//✔good
const item = {
value: 1,
addValue (val) {
return item.value + val
}
}
1.5. 对象属性简写
对象属性值的简写方式
原因:这样更简短且描述更清楚
const job = 'FrontEnd'
//❌bad
const item = {
job: job
}
//✔good
const item = {
job
}
1.6. 非法标识符定义
只对非法标识符的属性使用引号
原因:因为通常来说我们认为这样主观上会更容易阅读,这样会带来代码高亮上的提升,同时也更容易被主流 JS 引擎优化
//❌bad
const bad = {
'foo': 3,
'bar': 4,
'data-blah': 5
}
//✔good
const good = {
foo: 3,
bar: 4,
'data-blah': 5
}
1.7. 对象拷贝
优先使用对象展开运算符 ... 来做对象浅拷贝而不是使用 Object.assign,使用对象剩余操作符来获得一个包含确定的剩余属性的新对象
eslint规则:prefer-object-spread
//❌bad
const original = { a: 1, b: 2 }
const copy = Object.assign({}, original, { c: 3 }) // copy => { a: 1, b: 2, c: 3 }
//❌very bad
const original = { a: 1, b: 2 }
const copy = Object.assign(original, { c: 3 }) // this mutates `original` ಠ_ಠ
delete copy.a // so does this
//✔good
const original = { a: 1, b: 2 }
const copy = { ...original, c: 3 } // copy => { a: 1, b: 2, c: 3 }
const { a, ...noA } = copy // noA => { b: 2, c: 3 }
1.8. 属性访问
使用 . 来访问对象属性
eslint规则:dot-notation
const joke = {
name: 'haha',
age: 28
}
//❌bad
const name = joke['name']
//✔good
const name = joke.name
2. 代码逻辑规范
2.1. 使用变量前必须先声明
变量和函数声明被提升到作用域的顶部,因此可以在代码中的正式声明之前使用标识符。 这可能会令人困惑,最好在使用变量和函数之前始终声明它们。
//❌bad
alert(a);
var a = 10;
f();
function f() {}
//✔good
const a = 10;
function f() {}
alert(a);
f();
2.2. 使用 ES6 语法规范
开发中优先使用 ES6 中新增的语法糖和函数简化代码逻辑,并让代码灵活可复用
如箭头函数、await/async、解构、let、for...of 等等
2.3. switch使用
switch语句的每个case中做好防穿透处理,switch最后需要有default默认出口
eslint规则:default-case
//❌bad
switch (foo) {
case 1:
bar();
break;
default:
baz();
break;
case 2:
quux();
break;
}
//❌bad
switch (foo) {
case "x":
bar();
break;
default:
case "y":
baz();
break;
}
//✔good
switch (foo) {
case 1:
bar();
break;
case 2:
quux();
break;
default:
baz();
break;
}
2.4. 括号使用
代码中的逻辑块必须有大括号包裹,即使代码块的内容只有一行,涉及到一些判断和循环语句如 if、else、for、while、do、switch、try、catch、finally
//❌bad
if (condition) doSomething();
//✔good
if (condition) {
doSomething();
}
2.5. 条件判断和循环最多三层
条件判断能使用三目运算符和逻辑运算解决的就不要使用条件判断,但是不要写太长的三目运算,如果超过三层就要单独求值,并写清楚注释
eslint规则:no-nested-ternary
//❌bad
for (let i = 0; i < list.length; i++) {
...
for (let j = 0; j < list[i].children; j++) {
...
for (let k = 0; k < list[i][j].labels; k++) {
}
}
}
//✔good
for (let i = 0; i < list.length; i++) {
...
for (let j = 0; j < list[i].children; j++) {
...
checkLabel(list[i][j]) // 对于嵌套层级太深的做逻辑封装
}
}
//❌bad
getName() {
const customerType = this.customerType
if (customerType === 1) {
return '客户总数'
} else {
return '新增客户数'
}
}
//✔good
getName() {
return this.customerType === 1 ? '客户总数' : '新增客户数'
}
//❌bad
<template>
<div>
<!-- 过于复杂 三元运算 -->
<el-input
v-model="departName"
:disabled="
editForm.status == 1
? true
: editForm.status == 2
? true
: editForm.status == 3
? true
: false
"
/>
<!-- 以下这种判断很长的也禁止这样写 -->
<el-input
v-model="departName"
:disabled="
editForm.status == 1 ||
editForm.status == 2 ||
editForm.status == 3 ||
editForm.status == 4 ||
(editForm.status == 5 && editForm.age > 20)
"
/>
<!-- 更不能图省事直接在模板中写实际的操作代码 -->
<el-input
v-model="departName"
:disabled="
findStatus.some((item) => {
return item.status == editForm.status;
})
"
/>
</div>
</template>
//✔good
<template>
<div>
<!-- 简单的 一元运算 -->
<el-input v-model="name" :disabled="editForm.id" />
<el-input v-model="age" :disabled="editForm.age > 20" />
<!-- 简单的 三元运算 -->
<el-input