前端规范文档

变更记录

变更时间变更人变更标题变更内容
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-frameopen in new window

一、开发规范

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)文件夹、文件命名一律使用中划线分隔

(2)在每个文件的第一行要写 @file 文件注释,描述该文件的作用,如(文件名:createUserForm.vue)组件的引入请参考JS命名规范-组件命名方式

<!-- @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、常用变量对照表

变量有效场景含义
uaall浏览器 user-agent
wxOpenIdall微信 openId
startTimeall开始时间
endTimeall结束时间
createTimeall创建时间
total分页功能总数据条数
size分页功能每页条数
page分页功能当前页数
tableData分页功能列表数据
langall语言类型,zh_CN、en、zh_TW 等
isLoadingall是否正在加载数据
errall错误对象
errMsgall错误信息
i, j循环索引位置
formatDateall时间日期格式化
modalall模态框(页面中间弹出的窗口)
descall描述

二、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-infog-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

https://www.sass.hk/open in new window

四、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>

文档注释

/**
* 获取 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。

  • 一边写代码,一边写注释,而不是写完代码后再补注释。