《前端规范》
# 1. 前言
# 主要目的
1.提高代码的规范性
和可维护性
。通过保持代码风格和传统的一致性,我们可以减少遗留系统维护的负担,并降低未来系统崩溃的风险,减少团队成员切换项目的熟悉时间。
2.最佳实践
。通过遵照最佳实践,我们能确保优化的页面加载、性能以及可维护的代码。
# 核心思想
1.表现、内容和行为的分离 2.标记应该是结构良好、语义正确 以及 普遍合法 。 3.Javascript应该起到渐进式增强用户体验的作用 。
# 2. 命名规则
- 项目命名
- 目录命名
- Vue文件目录命名
- Js文件命名
- Css、Scss文件命名
- 图片命名
整体项目命名示例请查看示例文件夹
下的项目目录结构
。
# 项目命名
名称全部采用小写,尽量不要缩写,以中划线分隔。
命名格式:<业务类型>-<项目类型>-<端>
(1) 业务类型。如:
- 商家:merchant
- 创新业务:innovation
- 预订业务:booking
(2) 项目类型。如:
- 营销通:market
- 门店通:hotel
- 运营平台:operating
(3) 端。如:
- 移动端:mobile
- pc端:pc
- Android客户端:android
- Ios: ios
例:
营销通移动端:merchant-market-mobile 营销通pc端:merchant-market-pc
门店通移动端:merchant-hotel-pc 门店通pc端:merchant-hotel-h5
# 目录命名
名称全部采用小写,尽量不要缩写。有复数结构时,要采用复数命名法。
如:components、services、directives、filters、utils、styles。
# Vue文件目录命名
参考官方给出的:风格指南
1、文件夹命名。
1.1、 components 文件夹下的子文件夹统一使用PascalBase
风格
公用的业务组件放在最外层的components
文件夹下,如各业务组件需要抽离该业务下的组件,则放在各页面下的./components
文件夹下。
每个components文件夹下最多只有一层文件夹,且文件夹名称为组件名称,文件夹下必须有index.vue
或index.js
。
原因:公用组件使用PascalBase风格,除了是为了在引用组件时候能够风格统一,还能将引用的组件和.vue组件内部的使用驼峰法声明的变量加以区别。export { default as Sidebar } from './Sidebar'
1.2、除components文件夹下的文件名,其他文件目录名称统一使用kebab-case
格式。如活动中心模块(activity-center)、活动报名模块(activity-apply)。
2、组件文件命名(.vue文件):组件名应始终是多个单词的,除根组件APP.vue 和index.vue外。单文件组件的文件名始终是PascalBase
风格。如:SideBar.vue、NavBar.vue。
2.1 基础组件(应用特定样式和约定的基础组件,即仅用于展示的无逻辑无状态的组件)命名:以Base/APP作为特定的前缀
如:
BaseButton.vue
BaseTable.vue
BaseIcon.vue
2.2 紧密耦合的组件名,应以父组件名作为前缀名。
如:
SearchSidebar.vue
SearchSidebarNav.vue
# Js文件命名
单个单词命名的js文件,如index.js、date.js。
多个单词命名的js文件统一使用camelCase
驼峰命名法,如:dealErrorMsg.js、getToken.js。
# Css、Scss文件命名
名称全部采用小写,尽量使用一个单词说明,如有需要使用多个单词,则以kebab-case
格式。
公用css文件命名:
css reset文件: normalize.css/reset.css 全局公用的样式文件:common.css/app.css
注意:编写样式时多看看公用样式文件,看看是否已存在,避免样式冗余。如果有抽取了公用样式,记得及时跟其他小伙伴沟通。
组件css文件
当css样式过多,需要新建css文件时,应考虑新建文件夹,将.vue文件和css文件放在同一个文件夹内。此时可以使用index.vue和index.css文件名。
# 图片命名
名称全部采用小写,以中划线分隔,以图片类型作为前缀。
如: img-not-found.png icon-search.png
# 3. Git 使用规范
# 分支规范
分支管理参考git-flow(git-flow介绍)的工作流程,但不使用git-flow。所有的分支的操作都需要手工完成。
1.1、git-flow流程图: https://images.cnblogs.com/cnblogs_com/cnblogsfans/771108/o_git-flow-nvie.png
1.2、分支说明:
featrue/XXX
: 功能开发分支,继承自master分支,根据需求或者线上bug创建分支,开发、测试阶段所有改动只在该类型分支上修改;
dev
: 开发合并分支,合并来自feature/*
的分支代码,注意不要在此分支上做修改提交!!!只做合并操作!!!,并在此分支打包提交测试;
master
:发布分支,feature/*
测试通过的分支,合并到该分支,注意不要在此分支上做修改提交!!!只做合并操作!!!,并在此分支打包发布。
hotfix/XXX
: 热修复分支,继承自master分支,仅用于修复master分支上的问题。完成后合并到各分支。
bugfix/XXX
: bug修复分支,继承自master分支,仅用于修复master分支上的问题,完成后合并到各分支。
注意:当合并有冲突时,要解决冲突后再提交
,避免造成页面奔溃。不熟悉命令合并的同学可以使用编辑器的merge功能,或者可以使用工具,如:sourceTree。
# commit 规范
一般来说,commit message 应该清晰明了,说明本次提交的目的。我们参考目前比较流行的Angular规范
commit 格式:<type>: <subject>
(注意冒号后面有空格)。
(1)type: 用于说明 commit 的类别,无特殊情况下只使用下面7个标识。
- feat:新功能(feature)
- fix:修补bug
- docs:文档(documentation)
- style: 格式(不影响代码运行的变动)
- refactor:重构(即不是新增功能,也不是修改bug的代码变动)
- test:增加测试
- chore:构建过程或辅助工具的变动
(2)subject: commit 目的的简短描述,不超过50个字符。
例:
git commit -m "feat: 新增酒店刷新页面"
git commit -m "fix: 活动中心基本信息页活动ID不可选"
git commit -m "doc: readme文件添加项目打包说明"
2
3
注意:如果是有多种形式的修改,以最主要的修改选择type,其他修改可在subject中说明。
# 版本发布规范
已经测试完毕的功能分支合并到master
,按版本号打tag。
版本号组成::v<主版本号>.<子版本号>.<修订号>。
主版本号前加v标识表示version。如初始版本可为v1.0.0
或v0.1.0
管理策略:
- 当项目在进行了局部修改或 bug 修正时,主版本号和子版本号都不变,修正版本号加 1。如
v0.1.1
; - 当项目在原有的基础上增加了部分功能时,主版本号不变,子版本号加 1,修正版本号复位为 0。如:
v0.2.0
; - 当项目在进行了重大修改或局部修正累积较多,而导致项目整体发生全局变化时,主版本号加 1。如:
v1.0.0
;
版本说明:
每一次版本的发布都需要对该次版本上线进行说明,说明信息参考commit规范:
例:
git tag -a v0.1.0 -m "feat: 初始版本上线,包括首页、列表页、详情页等" master
建议:可在项目中添加版本说明文档,可方便查看生产环境当前版本。
参考文档:
# 4. JavaScript 规范
勤写注释,再语义化的命名不如一个中文注释
使用目前比较流行的Airbnb语法规范,并搭配Eslint作为语法检查,详情查看以下有关文档。
说明文档: Airbnb JavaScript Style Guide Airbnb JavaScript Style Guide中文翻译 eslint规则说明
提示:各种编辑器都有对应的Eslint插件,可以安装有关插件进行语法检查,如vscode支持文件保存时按照项目下的.eslintrc.js里定义的规则自动格式化,这样使得开发者在开发时并不需要过多的关注是否符合语法。
vscode配置eslint插件:vscode保存代码,自动按照eslint规范格式化代码设置
# 命名
1.变量
命名方式:采用小驼峰式命名法
。
命名规范:前缀应当是名词。(函数的名字前缀为动词,以此区分变量和函数)
命名建议:尽量在变量名字中体现所属类型,如:length、count等表示数字类型;而包含name、title表示为字符串类型。
如:
// Good
let maxCount = 10;
let tableTitle = 'LoginTable';
// Bad
let setCount = 10;
let getTitle = 'LoginTable';
2
3
4
5
6
7
2.常量
命名方式:必须采用全大写
的命名,且单词以_
分割,常量通常用于ajax请求url,和一些不会改变的数据。
命名规范:使用大写字母和下划线来组合命名,下划线用以分割单词。
如:
const MAX_COUNT = 10;
const URL = 'http://www.foreverz.com';
2
3.函数
命名方法:小驼峰式命名法
。
命名规范:前缀应当为动词。
命名建议:可使用常见动词约定
动词 | 含义 | 返回 |
---|---|---|
can | 判断是否可执行某个动作(权限) | 函数返回一个布尔值。true:可执行;false:不可执行 |
has | 判断是否含有某个值 | 函数返回一个布尔值。true:含有此值;false:不含有此值 |
is | 判断是否为某个值 | 函数返回一个布尔值。true:为某个值;false:不为某个值 |
get | 获取某个值 | 函数返回一个非布尔值 |
set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象 |
load | 加载某些数据 | 无返回值或者返回是否加载完成的结果 |
如:
//是否仅可读
function isReadonly() {
return true;
}
// 获取token
function getToken() {
return 'token';
}
2
3
4
5
6
7
8
4.类和构造函数
命名方法:大驼峰式命名法,首字母大写
。
命名规范:前缀为名称
4.1 类的成员
- 公共属性和方法:跟变量和函数的命名一样。
- 私有属性和方法:前缀为_(下划线),后面跟公共属性和方法一样的命名方式。
如:
class Person {
// 私有属性
private _name: string;
constructor() { }
// 公共方法
getName() {
return this._name;
}
// 静态方法
static _setName(name) {
this._name = name;
}
}
const person = new Person();
person.setName('mervyn');
person.getName(); // ->mervyn
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 语法
1.尽量不要混用语法
一般来说,项目开发使用规范应该统一。当使用了Es6规范时,如箭头函数,那么后续编写新函数的时候也应该使用箭头函数的方式。
注意点:
- 变量声明:使用const、let声明,则不使用var。
- 函数声明:使用箭头函数,则不使用function标识符命名。
- 模块引用:除webpack的配置文件外,其他地方使用了import、export作为模块导入导出,则不使用require、module.exports。(module.exports与exports,export与export default之间的关系和区别)
不好的示例:
let innId = '123';
var innName = '7天酒店';
const setNamet = (name) => {
return name;
}
function getName() {
return this.innName;
}
const foo = function () {
// ...
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.总是使用带类型判断的比较判断
总是使用===
精确的比较操作符,避免在判断的过程中,由 JavaScript 的强制类型转换所造成的困扰。
3.使用更简洁的实现方法
如:
// Bad
if (query.actName) {
this.map.actName = query.actName;
}
if (query.actStatus) {
this.map.actStatus = query.actStatus;
}
if (query.bookTime) {
this.map.bookTime = query.bookTime;
}
if (query.checkInTime) {
this.map.checkInTime = query.checkInTime;
}
if (query.quotaCrl) {
this.map.quotaCrl = query.quotaCrl;
}
if (query.rateCrl) {
this.map.rateCrl = query.rateCrl;
}
this.map.limit = parseInt(query.size || 10);
this.map.page = parseInt(query.page || 1);
// Good
const query = this.$route.query;
Object.keys(this.map).forEach(key => {
if (this.map.hasOwnProperty(key) && query[key]) {
if (key === 'bookType') {
this.map[key] = parseInt(query[key]);
} else {
this.map[key] = query[key];
}
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 书写规范
1.【缩进】:使用 4 个空格做为一个缩进层级。 2.【空格】 3.以eslint配置的rules为准。。。
# 5. vue 编写规范
参考官方给出的:风格指南
示例请查看示例文件夹
下的vue文件编写示例
。
# prop定义
- 在声明 prop 的时候,其命名应该始终使用 camelCase,而在模板和 JSX 中应该始终使用 kebab-case。
- prop定义尽量详细,至少要指定其类型
props: {
greetingText: {
required: true,
type: Boolean,
default: false
}
}
// 组件的引用统一使用 kebab-case 格式
<welcome-message greeting-text="hi"/>
2
3
4
5
6
7
8
9
10
# 为v-for 设置键值
原因:以便维护内部组件及其子树的状态
<ul>
<li
v-for="todo in todos"
:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
2
3
4
5
6
7
8
# 避免v-if 和v-for 用在一起
v-for比v-if有更高的优先级,所以每一次循环都会进行一次if判断,耗费性能。
# 多个特性的元素应该分多行撰写,每个特性一行。
<img
src="https://vuejs.org/images/logo.png"
alt="Vue Logo">
2
3
# 指令缩写 (用 : 表示 v-bind: 和用 @ 表示 v-on:) 应该要么都用要么都不用。
<input :value="newTodoText" :placeholder="newTodoInstructions"
>
2
# 元素特性顺序
1.定义 (提供组件的选项)
- is
2.列表渲染 (创建多个变化的相同元素)
- v-for
3.条件渲染 (元素是否渲染/显示)
- v-if
- v-else-if
- v-else
- v-show
- v-cloak
4.渲染方式 (改变元素的渲染方式)
- v-pre
- v-once
5.全局感知 (需要超越组件的知识)
- id
6.唯一的特性 (需要唯一值的特性)
- ref
- key
- slot
7.双向绑定 (把绑定和事件结合起来)
- v-model
8.其它特性 (所有普通的绑定或未绑定的特性)
9.事件 (组件事件监听器)
- v-on
10.内容 (覆写元素的内容)
- v-html
- v-text
# 组件/实例的选项顺序
这是我们推荐的组件选项默认顺序。它们被划分为几大类,所以你也能知道从插件里添加的新属性应该放到哪里。
1.副作用 (触发组件外的影响)
- el
2.全局感知 (要求组件以外的知识)
- name
- parent
3.组件类型 (更改组件的类型)
- functional
4.模板修改器 (改变模板的编译方式)
- delimiters
- comments
5.模板依赖 (模板内使用的资源)
- components
- directives
- filters
6.组合 (向选项里合并属性)
- extends
- mixins
7.接口 (组件的接口)
- inheritAttrs
- model
- props/propsData
8.本地状态 (本地的响应式属性)
- data
- computed
9.事件 (通过响应式事件触发的回调)
- watch 生命周期钩子 (按照它们被调用的顺序)
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- activated
- deactivated
- beforeDestroy
- destroyed
10.非响应式的属性 (不依赖响应系统的实例属性)
- methods
11.渲染 (组件输出的声明式描述)
- template/render
- renderError
# 组件样式
- 为组件样式设置作用域
- 避免在scoped中出现
元素选择器
,这样会导致性能变慢。(因为vue为了给样式设置作用域,会给元素添加一个唯一属性,如data-v-1234。当修改选择器的时候,只有完全匹配该特性才会生效。问题在于大量的元素和特性组合的选择器 (比如 button[data-v-f3f3eg9]) 会比类和特性组合的选择器慢,所以应该尽可能选用类选择器。 )
<style scoped>
...
</style>
2
3
# 6. css 规范
参考百度前端团队的css规范,并搭配stylelint作为语法检查,详情查看以下有关文档。
示例请查看示例文件夹
下的css编写示例
。
# 命名
- class 名称中只能出现小写字符和破折号(dashe)。破折号应当用于相关 class 的命名(例如,.btn 和 .btn-danger)。
- 避免过度任意的简写。.btn 代表 button,但是 .s 不能表达任何意思。
- class 名称应当尽可能短,并且意义明确。
- 基于最近的父 class 或基本(base) class 作为新 class 的前缀
- 编写公用样式时,应尽量做到不把页面样式耦合。合理的拆分样式,做到更灵活应用。
1.1 常用命名
页面:wrapper 页头: header 页面主体: main 页脚:footer 广告: banner 导航: nav 子导航: subnav 菜单: menu 子菜单: submenu 搜索: search 滚动: scroll ...
2.2 合理拆分样式
.btn-primary {
font-size: 12px;
color: #333333;
}
.btn-success {
color: #f9ec00;
}
.btn-error {
color: #f13d3d;
}
.flex-box{
display: flex;
}
.flex-item{
flex: 1;
}
.flex-align{
justify-content:center;
align-items:center;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 书写规范
1.【缩进】:使用 4 个空格做为一个缩进层级。
.selector {
margin: 0;
padding: 0;
}
2
3
4
2.【空格】
- 选择器 与 { 之间必须包含空格。
- 属性名 与之后的 : 之间不允许包含空格, : 与 属性值 之间必须包含空格。
- 列表型属性值 书写在单行时,, 后必须跟一个空格。
.selector {
margin: 0;
font-family: Arial, sans-serif;
}
2
3
4
3.【换行】
- 当一个 rule 包含多个 selector 时,每个选择器声明必须独占一行。
.post,
.page,
.comment {
line-height: 1.5;
}
2
3
4
5
4.【属性】
- 属性定义必须另起一行。
- 属性定义后必须以分号结尾。
/* good */
.selector {
margin: 0;
padding: 0;
}
/* bad */
.selector { margin: 0; padding: 0; }
2
3
4
5
6
7
5.【嵌套】:使用sass、less等编写样式时,嵌套最多不超过3层
.wrapper{
.container{
.title{
color: red;
}
}
}
2
3
4
5
6
7
6.更多请看上面有关文档介绍。。。
# 7. 示例
# 项目目录结构
目录结构:
- public // 静态资源文件
- src
- assets // 图片等资源
- common // 公用环境变量
- components // 公用组件
- Header
- index.vue
- index.js
- index.css
- directives // 全局directive
- filters // 全局filter
- pages // 业务组件
- demo-one
- components
- GoodsList
-index.vue
- goods-list.css
- GoodsList
-index.vue
- index.vue
- plugins // 全局插件
- store // vuex状态管理
- styles // 公用样式
- components
- app.css // import所有的公用样式
- common.css // 全局公用样式文件
- normalize.css // css reset(第三方文件,最好不好修改)
- variable.json // postcss 变量文件
- utils // 工具类js
- demo-one
- .env.development // 环境变量文件
- .env.production // 环境变量文件
- .env.test // 环境变量文件
- vue.config.js // webpack等配置文件
# vue文件编写示例
父组件
<template>
<div>
<goods-list goods-status="111" @get-goods-id="getGoodsId"/>
</div>
</template>
<script>
import GoodList from './GoodsList'
export default{
name: 'GoodCategory',
components: {
GoodList
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
子组件
<template>
<div class='demo-wrapper'>
<!-- 元素特性顺序 -->
<ul class="demo-list">
<li class="list-item"
v-for='item in itemList'
:key='item.id'
ref="`item_`${item.id}"
v-modle='keyword'
@click="emitGoodsId(item.id)">
<span>{{item.name}}</span>
</li>
</ul>
</div>
</template>
<script>
export default {
// 声明
name: 'GoodsList',
// 模板依赖
components: {},
directives: {},
filters: {},
// 组合(向内合并属性)
extends: {},
mixins: {},
// 接口
props: {
isDayUse: {
required: true,
type: Boolean,
default: false
}
},
// 本地的响应式属性
data: function() {
return {}
},
computed: {},
// 事件
watch: {},
// 生命周期
beforeCreate() {},
created() {},
beforeMount() {},
mounted() {},
beforeUpdate() {},
updated() {},
activated() {},
deactivated() {},
beforeDestroy() {},
destroyed() {},
methods: {
emitGoodsId(id) {
this.$emit('get-goods-id', id);
}
}
}
</script>
<style scoped>
.demo-list{
padding: 10px;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# css编写示例
/*
* flex
*/
.flex-wrapper{
display: flex;
}
.flex-item{
flex: 1;
}
.flex-align{
justify-content:center;
align-items:center;
}
/*
* 文字排版
*/
.text-right{
text-align: right!important;
}
.text-left{
text-align: left!important;
}
.text-center{
text-align: center!important;
}
/*
* 文字颜色
*/
.color-primary{
color: #ff0000;
}
.color-primary-light{
color: #f20000;
}
.color-primary-darker{
color: #ff40000;
}
.color-success{
color: #f3f3f3;
}
.color-error{
color: #e60a0a;
}
/*
* 布局排版 - 垂直居中
*/
.position-center-wrap{
position: relative;
}
.position-center{
position:absolute;
top:50%;
left: 50%;
transform: translate(-50%, -50%);
}
/*
* 浮动
*/
.pull-left{
float: left!important;
}
.pull-right{
float: right!important;
}
.clearfix:before,
.clearfix:after {
content: "";
display: table;
}
.clearfix:after {
clear: both;
}
.clearfix {
zoom: 1; /* ie 6/7 */
}
/*
* 图片自适应
*/
.img-responsive {
max-width: 100%;
width: 100%;
height: auto;
display: block;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# css常用命名
- about 关于
- account 账户
- arrow 箭头图标
- article 文章
- aside 边栏
- audio 音频
- avatar 头像
- bg,background 背景
- bar 栏(工具类)
- branding 品牌化
- crumb,breadcrumbs 面包屑
- btn,button 按钮
- caption 标题,说明
- category 分类
- chart 图表
- clearfix 清除浮动
- close 关闭
- col,column 列
- comment 评论
- community 社区
- container 容器
- content 内容
- copyright 版权
- current 当前态,选中态
- default 默认
- description 描述
- details 细节
- disabled 不可用
- entry 文章,博文
- error 错误
- even 偶数,常用于多行列表或表格中
- fail 失败(提示)
- feature 专题
- fewer 收起
- field 用于表单的输入区域
- figure 图
- filter 筛选
- first 第一个,常用于列表中
- footer 页脚
- forum 论坛
- gallery 画廊
- group 模块,清除浮动
- header 页头
- help 帮助
- hide 隐藏
- hightlight 高亮
- home 主页
- icon 图标
- info,information 信息
- last 最后一个,常用于列表中
- links 链接
- login 登录
- logout 退出
- logo 标志
- main 主体
- menu 菜单
- meta 作者、更新时间等信息栏,一般位于标题之下
- module 模块
- more 更多(展开)
- msg,message 消息
- nav,navigation 导航
- next 下一页
- nub 小块
- odd 奇数,常用于多行列表或表格中
- off 鼠标离开
- on 鼠标移过
- output 输出
- pagination 分页
- pop,popup 弹窗
- preview 预览
- previous 上一页
- primary 主要
- progress 进度条
- promotion 促销
- rcommd,recommendations 推荐
- reg,register 注册
- save 保存
- search 搜索
- secondary 次要
- section 区块
- selected 已选
- share 分享
- show 显示
- sidebar 边栏,侧栏
- slide 幻灯片,图片切换
- sort 排序
- sub 次级的,子级的
- submit 提交
- subscribe 订阅
- subtitle 副标题
- success 成功(提示)
- summary 摘要
- tab 标签页
- table 表格
- txt,text 文本
- thumbnail 缩略图
- time 时间
- tips 提示
- title 标题
- video 视频
- wrap 容器,包,一般用于最外层
- wrapper 容器,包,一般用于最外层