前端规范文档
变更记录
变更时间 | 变更人 | 变更标题 | 变更内容 |
---|---|---|---|
2021-07-12 | 赵志星 | 命名规范 | 4.1 JS文件命名规范 |
2021-07-13 | 赵志星 | 命名规范 | 1.2.1 文件夹创建命名规范4.1 JS文件命名规范 |
2021-07-26 | 赵志星 | CSS预处理器 | 3.4 CSS预处理器 |
2021-12-22 | 赵志星 | 变量、属性、方法命名规则补充 | 1.4 变量、属性、方法命名1.5 常用变量对照表 |
2022-04-18 | 赵志星 | 命名规范 | 1.2.1 文件夹创建命名规范4.1 JS文件命名规范 |
示例:https://codeup.aliyun.com/5f009f6e6a575d7f23661045/frontend/web-frame
一、开发规范
1、文件存放目录
|-- public 入口HTML, 使用 link 和 script 标签引入的第三方 css 和 js
|-- src
|-- assets 项目内静态资源
| |-- images 图片资源
| |-- styles 样式资源
| |-- fonts 字体资源
|-- components 项目全局组件
| |-- xxxx.vue
| |-- index.js 项目全局组件入口文件
|-- pages 页面入口, 一般有index 和 login
| |-- index 项目主页面
| |-- login 登录页面
|-- router 路由
| |-- index.js 路由入口文件
| |-- xxxxx.js 其他模块路由文件, 在 router/index.js 文件中引入, 以模块名命名
|-- static 通过 import xxx from 'xxx' 引入的第三方模块, 如: 直播SDK
|-- store VUEX, store(数据处理主要放在这一层)
| |-- module 各模块的store存放位置, 安装功能模块分目录
| |-- index.js store的全局入口
|-- utils 全局工具方法
| |-- ajax.js 通用请求方法
| |-- directive.js 自定义指令
| |-- error-code.js 错误处理code列表
| |-- utils.js 通用工具方法
| |-- filter.js 全局过滤器
|-- views 页面
|-- index.vue 首页(也可能不存在此页面)
|-- 模块一 按模块分目录
|-- 模块一/页面一 每一个页面使用一个文件夹目录
|-- index.vue 页面一的具体内容
|-- components 只在页面一使用的内部组件
2、文件创建、命名规范
(1)文件夹、文件命名一律使用中划线分隔
@file
文件注释,描述该文件的作用,如(文件名:createUserForm.vue)组件的引入请参考JS命名规范-组件命名方式
:
(2)在每个文件的第一行要写 <!-- @file 创建用户表单 -->
<template>
<div class="c-create-user-form"></div>
</template>
3、关于依赖包安装
需要用到新的依赖包,需向项目负责人报备,都统一由项目负责人负责安装进项目中。
4、变量、属性、方法命名
变量、属性、方法命名遵循以下规则:
(1)优先使用对照表中的变量
(2)动词优先。如:日期格式化,应该使用 formatDate 而不是 dateFormat。类型变化应该是 changeType 而不是 typeChange
(3)变量应该能描述存放的数据。例如:产品列表,不要使用 list 这种通用称呼,尽量使用 productList 这种别人一看就懂的命名
(4)数据类型的补充。可以用 List,Obj,Str,Num等后缀描述某些变量的类型。例如:使用人数、使用者列表可以分别用变量:userNum、userList 表示。
(5)用途描述。可以使用一些前缀描述变量或者方法的用途。例如控制显隐的变量可以用 is 描述,如:isModalShow、isListShow。数据请求、数据保存、数据删除的方法可以用get、set、save、delete等前缀描述,如:getProductData、saveUserInfo、deleteUser
5、常用变量对照表
变量 | 有效场景 | 含义 |
---|---|---|
ua | all | 浏览器 user-agent |
wxOpenId | all | 微信 openId |
startTime | all | 开始时间 |
endTime | all | 结束时间 |
createTime | all | 创建时间 |
total | 分页功能 | 总数据条数 |
size | 分页功能 | 每页条数 |
page | 分页功能 | 当前页数 |
tableData | 分页功能 | 列表数据 |
lang | all | 语言类型,zh_CN、en、zh_TW 等 |
isLoading | all | 是否正在加载数据 |
err | all | 错误对象 |
errMsg | all | 错误信息 |
i, j | 循环 | 索引位置 |
formatDate | all | 时间日期格式化 |
modal | all | 模态框(页面中间弹出的窗口) |
desc | all | 描述 |
二、HTML规范
1、属性名
(1)属性名不可以使用驼峰,要使用中划线分隔
(2)属性名顺序
1、 指令
2、 普通属性
3、 v-bind属性
4、 事件监听
(3)属性数量超过三个,属性名要换行写
(4)多个class涉及判断、三元表达式时,使用数组语法,示例如下:
// 标签
<form
label-width="100px"
:model="form"
:class=[waitClass, endClass]
@confirm="handleSubmit"
>
computed: {
waitClass() {
retrun this.status === ‘live’ ? ‘wait-class’ : ‘’
},
endClass() {
retrun this.status === ‘end’ ? ‘end-class’ : ‘’
}
}
三、CSS规范
1、样式类名
(1)命名
采用全小写、多个单词间用连字符分隔的命名方式,例如 c-basic-info
、g-img-cover
。
(2)前缀
为了便于区分样式类用途,减少样式类名冲突,项目中所有样式类名都必须前缀,规则为:
页面样式,以 p- 开头,例如
p-role-setting
,角色设置页。全局样式,以 g- 开头,例如
g-boundary
。注意全局样式只允许写到src/assets/styles
的样式文件内。组件样式,以 c- 开头,例如
c-user-table
。
样式类名采用 BEM(Block Element Modifier) 规则,Block 和 Element 之间用双下划线隔开,Block(或 Element) 与 Modifier 之间用双连字符隔开。示例:
<div class="c-main">
<div class="c-main__inner">
<ul class="c-main__list">
<li class="c-main__list__item">Item 1</li>
<li class="c-main__list__item c-main__list__item--highlighted">Item 2</li>
</ul>
</div>
</div>
最外层为块(Block),类名为 c-main
,inner 是 main 的元素(Element),因而命名为 c-main__inner
。如果层级太深导致类名太长,可以省略一些次要的层级,例如 list 的类名省略了inner 这一层,命名为 c-main__list
。但是,省略层级时要留意是否会造成类名冲突。最后,对于 list 里面高亮的 item,可以命名为 c-main__list__item--highlighted
,因为高亮是一种修饰(Modifier)。
(3)组件的样式类名
组件的样式类必须跟随目录命名,例如目录名为 form,则该组件命名为 c-form
。如果此组件太大需要分离成几个小组件(文件),则小组件的对应命名为 c-form__文件名
。
2、不要使用 SASS 的 & 简化选择器
如下的写法将导致无法通过文本搜索找到 .c-header__nav 和 .c-header__login,不利于样式定位:
.c-header {
&__nav {}
&__login {}
}
所以不要使用上面的写法,宁愿写长一些:
.c-header__nav {}
.c-header__login {}
3、原子样式类使用规则
(1)什么是原子样式类
全局可以使用,常用通用的样式类,只有简单的样式规则,可以通过类名知道样式作用
例如:
.gl-mb-20 {
margin-bottom: 20px;
}
.gl-text-center {
text-align: center;
}
(2)数量不得超过3个
标签内的原子样式类不得超过3个,超过3个时需要命名 className 来写对应的样式。
<!-- Good -->
<div class="gl-w-100 gl-mb-20 gl-p-30">这是一行文本</div>
<!-- Bad -->
<div class="gl-w-100 gl-mb-20 gl-p-30 gl-text-center">这也是一行文本</div>
(3)不得与其他非原子样式类同时使用
<!-- Bad -->
<div class="c-create-user-form__title gl-text-center">复制直播间</div>
注意,该规则在特殊情况下允许同时使用,如 className 是列表中多个地方使用,但其中一个的距离是不同。
<!-- OK -->
<div class="c-todo-item">to do 1</div>
<div class="c-todo-item gl-mb-20">to do 2</div>
<div class="c-todo-item">to do 3</div>
<div class="c-todo-item">to do 4</div>
<div class="c-todo-item">to do 5</div>
4、CSS预处理器
由于移动端框架uni-app对Less的支持不是很好,所有项目CSS预处理器统一使用Sass
四、JS规范
1、关于组件命名方式
文件夹、文件命名多个单词使用中划线分隔,如 create-user-form.vue
,在引入时的,不使用文件名后缀,组件变量以大驼峰方式命名,组件标签以横线命名,如:
// 标签
<create-user-form />
// 引入
import CreateUserForm from '~url/create-user-form'
exprt default{
components: {
CreateUserForm
}
}
*注意:组件的命名应该注意不要与 HTML 标签重名。不要将组件命名为:title、div、frame、from、time.....等与 HTML 标签重名的组件,可以通过增加前缀,或者模块名字等方式解决。如:lkTitle、questionTitle
2、关于 vue api 顺序
已 eslint 的 vue/user
为准,同时各 api 之间需要添加换行,如:
// 错误的
{
data() {},
methods: {},
mounted() {},
computed: {}
}
// 正确的
{
data() {},
computed: {},
mounted() {},
methods: {},
}
3、store使用
(1)页面的展示数据、API调用等数据操作都在store层进行,如:
export default {
namespaced: true,
state: {
apiGetData: {} // 调用接口返回的参数
},
// 同步方法
mutations: {
setApiGetData(state, apiGetData) {
state. apiGetData = apiGetData
}
},
// 异步方法
actions: {
/**
* 获取数据
* @param {*} ctx
* @param {String} id 数据ID
*/
async isVerification(ctx, { id } = {}) {
const apiGetData = await ajax.postStream('/api/xxxxxx, {
id
})
ctx.commit('setApiGetData', apiGetData)
return apiGetData
}
}
}
4、API调用
(1)async-await 来写异步操作
(2)在store层调用API,不在view层直接调用API
五、注释规范
1、注释分类
重复型注释
用不同的词语,重申代码的内容
// 数组为空直接返回
if (!ary.length) return;
// 判断是否为移动端
If (/Android|webOS|iPhone|iPod|BlackBerry/i.test(userAgent)) {
next(mobileHintPath);
return true;
}
// 数组为空直接返回
if (!ary.length) return;
// 判断是否为移动端
If (/Android|webOS|iPhone|iPod|BlackBerry/i.test(userAgent)) {
next(mobileHintPath);
return true;
}
解释型注释
解释复杂代码的实现。
// 设计规范:浏览器器可⽤用区域宽度在 [1280, 1920] 区间内变化时,
// 内部区域宽度需等⽐比缩放,变化区间为 [1180, 1680]。
// 因为 CSS 中⽆无法进⾏行行这个计算,所以在 js 中计算后设为 html 的 font-size,
// CSS 中⽤用到这个尺⼨寸时只需要写 1rem 即可。document.documentElement.style.fontSize =
1180 +
(1680 - 1180) *
(Math.min(1920, Math.max(width, 1280)) - 1280) / (1920 - 1280) + 'px';
标记型注释
TODO:如果代码中有该标识,说明在标识处有功能代码待编写。
FIXME:如果代码中有该标识,说明标识处代码需要修正。甚至代码是错误的,不能工作,需要修复。
XXX:如果代码中有该标识,说明标识处代码虽然实现了功能,但是实现的方法有待商榷,希望将来能改进。
async function getList() {
// TODO 后端接口未开发
}
总结型注释
将一整块代码总结为几句话。
// 部分接⼝口的 sign 值计算
export function genSign(data, secret = 'polyvChatSign') {
let text = '';
const keys = Object.keys(data).sort();
for (const key of keys) {
text += `${key}${data[key]}`;
}
const sign = md5(secret + text + secret);
return sign.toUpperCase();
}
意图型注释
解释编写代码的原因或目的
let sessionStorage, localStorage;
// Chrome 隐私模式,跨域 iframe 内访问本地存储的相关对象会抛出异常
try {
sessionStorage = window.sessionStorage;
localStorage = window.localStorage;
} catch (e) {
}
讨论:哪些注释有用
重复型注释:这类注释基本没有用处,无需画蛇添足编写
解释型注释:此类注释前应先考虑简化代码,实在无法简化时再编写注释
标记型注释:适当使用,TODO 和 FIXME 的事项应完成后再上线
总结型注释:真正意义上的好注释
意图型注释:能有效帮助他人快速理解代码
2、规范
文件注释
总是给每个源码文件编写文件头注释,除非该文件只输出一个类。
/**
* @file ⽂文件作⽤用概述(总结型注释)for js & css
*/
<!-- @file ⽂文件作⽤用概述(总结型注释)for Vue.js component -->
<template></template>
<script></script>
<style></style>
文档注释
内部 SDK、外部 SDK,以及项目中的公共代码,其提供的接口必须带有文档注释。
JS 注释格式参考 JSDoc(https://jsdoc.app/)。
TS 注释格式参考 TypeDoc(https://typedoc.org/)。
Vue.js 组件注释格式参考 VueDoc(https://gitlab.com/vuedoc/parser#syntax)。
/**
* 获取 cookie。
* @param {string} name cookie 名。
* @return {string} cookie 值。
*/
function getCookie(name) { }
/**
* 移除 cookie。 * @param {string} name cookie 名。
* @param {Object} [options] 参数。
* @param {string} [options.domain] 所在域。
* @param {string} [options.path] 所在路路径。
*/
function removeCookie(name: string) { }
变量
一些约定俗成的命名,只要在上下文中没有歧义,就不需要编写注释。否则需要注明含义和用途。考虑给变量以及与其连续的、有关联的代码段编写总结型注释,这种情况下无需单独给变量写注释。
// 常⽤用属性名到特性名的映射,⽤用于设置特性时使⽤用正确的特性名
const attrToProp = Object.create(null);
attrToProp['for'] = 'htmlFor';
attrToProp['class'] = 'className';
['tabIndex', 'readOnly', 'maxLength', 'cellSpacing', 'cellPadding', 'rowSpan', 'colSpan', 'useMap', 'frameBorder', 'contentEditable', 'isMap'
].forEach(function(item) {
attrToProp[item.toLowerCase()] = item;
});
约定⸺函数
考虑给每个函数编写总结型注释,说明函数功能和用途。
当函数名足够语义化,且函数体不多于 10 行时,可以省略此注释。
函数的内部实现,除非已经足够语义化,否则需要适当编写注释。
代码语义化
function addClass(node, className) {
// HTML 元素才⽀支持设置样式类,不不对⾮非 HTML 元素操作
if (node == null || node.nodeType !== 1 || !('style' in node)) {
return;
}
node.className = node.className + ' ' + className;
}
function isHTMLElement(node) {
return node != null && node.nodeType === 1 && ('style' in node);
}
function addClass(node, className) {
if (!isHTMLElement(node)) { return; }
node.className = node.className + ' ' + className;
}
后端接口调用
封装后端接口调用为函数或方法,在注释中附带接口文档地址。
// 一次获取多个频道的直播状态
// http://live.polyv.cn/doc.html#/menuLi/v2/watch/channel/multi-meeting/live-status
export function getChannelStatus({ channelIds, viewerId }) {
return apiLive.get('v2/watch/channel/multi-meeting/live-status', {
data: {
channelIds,
viewerId
}
});
}
约定⸺类
给每个类编写总结型注释。
类的构造函数和方法参照函数约定。
类的属性参照变量约定。
约定⸺Vue.js组件&Vuex
data、props、state:按变量的约定。
watch:针对每一项编写意图型注释。
methods、filters、computed、mutations、actions:按函数的约定。
这些注释不要写
重复型注释
正确性存疑的注释
// 代码不是我写的,注释是我补充的,写的对不对我也不清楚
修改时间、作者等 GIT 操作记录中具备的信息
原则
如无必要,勿写注释;如有必要,尽量详细 。
多谈 what和 why,少说 how。
一边写代码,一边写注释,而不是写完代码后再补注释。