前端代码规范

语言: CN / TW / HK

最近组内经常进行CodeReview,于是参考一些大厂规范以及一些开源的优秀源码,整理了一些前端代码规范,帮助我们后续可以写出更好维护的代码。有什么不对的地方,欢迎大家指出,一起学习进步!

先来看下下面这张思维导图:

前端代码开发规范思维导图 (1).jpg

下面会就几个方面展开来说。

命名规范

驼峰式命名法介绍

  • Pascal Case大驼峰式命名法:首字母大写。eg:PersonInfo
  • Camel Case小驼峰式命名法:首字母小写。eg:PersonInfo

文件命名

  • 所有文件名统一使用小写,首页命名为index.xxx,文件名禁止特殊字符比如空格、$等。统一使用英文单词或拼音缩写,必须小写。( 为了醒目,某些说明文件的文件名,可以使用大写字母,比如README、LICENSE。 )
  • 文件名包含多个单词时,单词之间建议使用半角的连词线 ( - ) 分隔。
  • 文件目录结构嵌套层级不要过深

变量命名

命名方式 : 小驼峰式命名方法命名规范 : 类型+对象描述的方式,如果没有明确的类型,就可以使前缀为名词

| 表头 | 表头 | | -------- | -- | | array | a | | boolean | b | | function | fn | | int | i | | object | o | | regular | r | | string | s |

函数命名

命名方式 : 小驼峰方式 ( 构造函数使用大驼峰命名法 ) 命名规则 : 前缀为动词

| 动词 | 含义 | 返回值 | | --- | ------------------ | ----------------------------- | | can | 判断是否可执行某个动作 ( 权限 ) | 函数返回一个布尔值。true:可执行;false:不可执行 | | has | 判断是否含有某个值 | 函数返回一个布尔值。true:可执行;false:不可执行 | | is | 判断是否为某个值 | 函数返回一个布尔值。true:可执行;false:不可执行 | | get | 获取某个值 | 函数返回一个非布尔值 | | set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象 |

例子:

// 是否可跳舞 function canDance(){ return true; } ​ // 获取工作 function getWork{ return this.work }

常量命名

命名方法 : 全部大写命名规范 : 使用大写字母和下划线来组合命名,下划线用以分割单词。

例子:

const PATH = "xxxx"

类命名

  • 公共属性和方法 : 同变量命名方式
  • 私有属性和方法 : 前缀为下划线(_或#)后面跟公共属性和方法一样的命名方式

注释规范

单行注释和多行注释的空格保存代码时eslint会帮我们处理,不用手动加空格。

单行注释 ( // )

  • 单独一行://(双斜线)与注释文字之间保留一个空格
  • 在代码后面添加注释://(双斜线)与代码之间保留一个空格,并且//(双斜线)与注释文字之间保留一个空格。
  • 注释代码://(双斜线)与代码之间保留一个空格

多行注释 ( / 注释说明 / )

  • 若开始(/*和结束(*/)都在一行,推荐采用单行注释
  • 若至少三行注释时,第一行为/*,最后行为*/,其他行以*开始,并且注释文字与*保留一个空格。

函数(方法)注释

函数(方法)注释也是多行注释的一种,但是包含了特殊的注释要求

/** * 函数说明 * @关键字 */

常用注释关键字

| 注释名 | 语法 | 含义 | 示例 | | -------- | ----------------------- | --------- | ------------------------- | | @param | @param 参数名 {参数类型} 描述信息 | 描述参数的信息 | @param name {String} 传入名称 | | @return | @param 参数名 {参数类型} 描述信息 | 描述返回值的信息 | @param name {String} 传入名称 | | @author | @author 作者信息 [附属信息:如邮箱] | 描述返回值的信息 | @author 李四 2022/12/16 | | @version | @version XX.XX.XX | 描述此函数的版本号 | @version 1.1.1 | | @example | @example 示例代码 | 描述此函数的版本号 | |

文件目录结构

同功能放在同一个文件目录下,目录结构不要嵌套过深,文件名语义化一些,方便后续维护。

  • 文件夹名称全部采用小写+"-" 来隔开;
  • 避免多层嵌套,单个项目中的目录嵌套控制在最多三到四个层级内;

例子:

- src 开发目录     - pages 视图         - module-a 模块A           - components 私有组件             - ComA.vue             - ComB.vue           - index.vue         - module-b 模块B     - components 公共组件       - index.vue 导出所有组件       - header         - index.vue     - utils 这里是以utils为后缀,JS工具库       - index.js       - a.utils.js       - b.utils.js     - hooks 这里是以hooks为后缀       - index.js       - a.hooks.js       - b.hooks.js     - service api请求,这里是以api为后缀       - a.api.js 按照后端微服务进行划分       - b.api.js     - constans 常量

通过对工具函数、hooks、api等加上后缀,更加容易区分引入的文件。

代码规范

JS

JS/TS主流的大致有这几种:

可以参考star最多的进行配置,几乎覆盖了JavaScript的每一项特性。

下面会就代码层面作出阐述。

| 名称 | 说明 | | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 命名规范 | 1. 普通命名采用小驼峰式命名
2. 命名是复数的时候需要加s
3. 命名需要符合语义化,尽量减少缩写的情况发生,做到见名知意,如果函数命名,可以采用加上动词前缀 | | 变量规范 | 1. ​​变量定义​尽量使用const、let
2. ​变量兜底 | | 字符串 | 1. ​统一使用单引号而不是双引号,配置安装eslint后,保存会自动格式化处理
2. ​用字符串模板而不是'+'来拼接字符串
3. ​不要使用不必要的转义字符
4. ​不要在字符串中用eval(),漏洞太多 | | 数组 | 1. 用扩展运算符(...)做数组浅拷贝
2. ​使用数组解构 | | 对象 | 1. ​ES6 使用属性值缩写,​将属性的缩写放在对象声明的开头
2. ​对象浅拷贝时,更推荐使用扩展运算符 ...使用对象解构 | | 函数 | 1. ​函数参数使用默认值替代使用条件语句进行赋值
2. 函数参数越少越好,如果参数超过两个,要使用 ES6 的解构语法,不用考虑参数的顺序。把默认参数赋值放在最后
3. ​尽量使用箭头函数
4. ​用命名函数表达式而不是函数声明,​函数声明作用域会提升,降低了代码可读性和可维护性
5. ​不要改参数,不要对参数重新赋值
6. ​功能函数使用纯函数,输入一致,输出结果永远唯一
7. ​优先使用函数式编程 | | for循环 | 使用for循环过程中,数组的长度,使用一个变量来接收, ​有利于代码执行效率得到提高,而不是每走一次循环,都得重新计算数组长度|

Vue

遵循vue.js官方风格指南,https://vuejs.bootcss.com/style-guide/

组件

| 名称 | 说明 | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 组件命名 | ​1. 组件名为多个单词,命名为组件用途,完整单词的组件名(倾向于完整单词而不是缩写)
​2. 文件名应该要么始终是单词大写开头 (PascalCase),要么始终是横线连接 (kebab-case),例如:todo-item或TodoItem
3. ​基础组件名, 应用特定样式和约定的基础组件 (也就是展示类的、无逻辑的或无状态的组件) 应该全部以一个特定的前缀开头,比如Base、App或V
4. ​单例组件名,只应该拥有单个活跃实例的组件应该以The前缀命名,以示其唯一性
5. ​紧密耦合的组件名, ​和父组件紧密耦合的子组件应该以父组件名作为前缀命名
6. 组件名中的单词顺序, 组件名应该以高级别的 (通常是一般化描述的) 单词开头,以描述性的修饰词结尾
7.模板中的组件名大小写,​对于绝大多数项目来说,在单文件组件和字符串模板中组件名应该总是PascalCase的——但是在DOM模板中总是kebab-case的
8. ​JS/JSX中的组件名大小写,JS/JSX 中的组件名应该始终是 PascalCase 的,尽管在较为简单的应用中只使用Vue.component进行全局组件注册时,可以使用kebab-case字符串 | | 组件数据 | ​组件的data必须是一个函数 | | props定义 | ​​1. prop定义应该尽量详细 ​
2. prop名大小写,​在声明prop的时候,其命名应该始终使用camelCase,而在模板和JSX中应该始终使用kebab-case。 | | v-for使用 | 1. 为v-for设置键值,尽量避免使用index作为key ​
2. 避免v-if和v-for用在一起 | | 样式 | ​​为组件样式设置作用域,使用scoped属性,使用BEM约定 | | 私有property名 | 1. Vue使用前缀来定义其自身的私有 property
2. 推荐使用$
,作为一个用户定义的私有property的约定,以确保不会和Vue自身相冲突 | | 自闭合组件 | 在单文件组件、字符串模板和JSX中没有内容的组件应该是自闭合的——但在DOM模板里永远不要这样做 | | attribute | 1. 多个attribute的元素应该分多行撰写,每个attribute一行 ​
2. 带引号的attribute值 | | 模板中简单的表达式 | 组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法 | | 简单的计算属性 | ​应该把复杂计算属性分割为尽可能多的更简单的property,计算属性不能产生副作用 | | 指令 | 指令缩写 (用:表示 v-bind:、用@表示 v-on: 和用#表示 v-slot:) | | 组件通信 | 应该优先通过prop和事件进行父子组件之间的通信,而不是this.$parent或变更prop | | 事件、定时器 | 清除定时器或者事件监听 | | 代码文件 | 开发过程中单个文件不允许超过600行,特别复杂的功能,文件不允许超过1000行 |

模板中的组件名大小写

PascalCase相比kebab-case有一些优势:

  • 编辑器可以在模板里自动补全组件名,因为PascalCase同样适用于JavaScript
  • 视觉上比 更能够和单个单词的HTML元素区别开来,因为前者的不同之处有两个大写字母,后者只有一个横线
  • 如果你在模板中使用任何非Vue的自定义元素,比如一个Web Component,PascalCase确保了你的Vue组件在视觉上仍然是易识别的

由于HTML是大小写不敏感的,在DOM模板中必须仍使用kebab-case。

例子:

```

```

Prop名大小写

我们单纯的遵循每个语言的约定。在JavaScript中更自然的是camelCase。而在HTML中则是kebab-case。

文件目录

| 名称 | 说明 | | ---- | --------------------------------------- | | 资源 | 资源统一放置在 assets 文件夹下,资源以文件夹组织,文件夹名称即模块名称 | | 公共方法 | 放置在utils内部 | | 样式 | BEM命名规范 |

CSS

CSS检查代码规范

使用stylelint插件,规范则推荐使用stylelint-config-standard

下面简单说下stylelint-config-standard使用

1. 安装 yarn add -D stylelint stylelint-config-standard ​ 2. 在项目的根目录中创建一个配置文件.stylelintrc.json,内容如下: { "extends": "stylelint-config-standard" } ​ 3. 解决与prettier配置的冲突: ​ yarn add -D stylelint-config-prettier ​ 4. 将下面配置复制到.stylelintrc.json中: { "extends": ["stylelint-config-standard", "stylelint-config-prettier"] } ​ 5. 在 git commit 阶段进行检测: "lint-staged": {   "**/*": "prettier --write --ignore-unknown", // 格式化   "src/**.{js,jsx,ts,tsx}": "eslint --ext .js,.jsx,.ts,.tsx", // 对js文件检测   "**/*.{less,css}": "stylelint --fix" // 对css文件进行检测 },

BEM命名原则

  • block:模块,名字单词间用-连接
  • element:元素,模块的子元素,以__与block连接
  • modifier:修饰,模块的变体,定义特殊模块,以--与block连接

有效使用css选择器

有效使用css选择器,需遵循以下原则:

  • 保持简单,不要使用嵌套过多过于复杂的选择器,选择器嵌套应少于3级;
  • 通配符和属性选择器效率最低,需要匹配的元素最多,尽量避免使用;
  • 避免使用CSS表达式;
  • 慎重选择高消耗的样式(高消耗属性在绘制前需要浏览器进行大量计算),避免重绘重排;
  • ​css选择器中避免使用标签名;
  • ​尽量使用缩写属性;
  • ​使用子选择器;
  • 0后面不带单位;
  • id和class,命名​名称语义化,不要过于简单,防止模块之间样式互相影响;​​合理的使用id,​​一般情况下id不应该被用于样式,并且id的权重很高,所以不使用id解决样式的问题,而是使用class;

CodeReview常见代码问题汇总

| 类别 | 描述 | 说明 | | ---- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 文件 | 命名 | 1. 组件命名规范,尽量不要和现有组件或远程组件重合,比如页面里使用组件时直接使用Table
2. 文件命名尽量语义化,如果没有定制化的,文件命名不要太定制化,比如文件命名直接是fifth-floor
3. 文件中变量/方法命名语义化,方法名格式统一 | | UI规范 | 删除按钮颜色 | 删除类的操作按钮颜色使用红色 | | vue | 代码问题 | 1. 组件数据共享: 嵌套调用的组件声明一个方法,直接返回当前模块的数据,不要层层嵌套,通过$refs获取,比如:组件里定义一个getValues方法获取数据
2. 生命周期钩子:vue生命周期如果没有依赖关系的话,尽量不要用async/await,可以把请求封装成一个方法,生命周期中直接调用方法;如果有依赖关系的话就用,看场景
3. v-for中key值绑定,尽量不要绑定index,使用id,如果没有id且不涉及添加删除操作时,可以绑定index
4. 使用vue/composition-api时,相同变量以及方法考虑是否放在一起,方便看,看场景及个人习惯
5. 引入第三方工具包时尽量使用小的包,比如moment包换成dayjs
6. 使用动态路由(id)
7. 不建议this传递,问题排查容易出问题,即在其他页面修改this里的变量 | | JS | 代码问题 | 1. map/forEach: 注意区分两者使用场景,不要随便使用
2. 常量:页面里使用多次的字符统一使用常量映射,不要直接在页面中使用字符
3. 代码简洁性:避免使用多次循环列表,如果列表数据很多会有性能问题,比如filter和map嵌套使用
4. 三元表达式: 使用时根据场景可以换成或
5. 否定前置
6. 空数据:接口返回数据为空兼容判断
7. 数组遍历for循环修改为for/of
8. dayjs/moment可以转换一切时间形式为format,不止是时间戳还有标准时间
9. 空值合并运算符(??)使用: 当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数
10. dayjs().format('YYYY-MM-DD HH:mm:ss')无参数时默认取的当前时刻; 有参数时可以考虑把参数提取出来
11. toString()可以写为join(',')
12. 数据为空兼容:用或、?.表示
13. if switch可以考虑转换为json map形式
14. a或b或c改为 [].includes()
15. try/catch捕获错误异常console.dir(error); | | ES6 | 代码问题 | 1. map循环中可以使用解构的话换成解构,避免多层嵌套
2. 解构:能解构尽量解构,边界值兼容处理 | | CSS | 代码问题 | 1. 类名:类名注意不要太简单,直接取name/title/desc之类的,容易和其他人写的类名冲突,比如别人写了同名的类名没有设置scoped或者全局类名,会影响自己的样式 |

map/forEach说明

比如:

```

demo1

arr.map(({value = {}, ...item})=>{    return {          ...item,          ...value    }}) ​

demo2

const type = this.alarmType?.map(item => ({alarmTypeId: item})) || []; ​

demo3 push行为修改为map

this.evidence.printScreen = data.printScreen?.map( ({ id, title, value }) => ({   id,   title,   value,   url: ${imgUrlPre}${id}, }) ); ```

dayjs.format(str)说明

dayjs.format(str) ,str抽离成下面形式 const DATE_FORMAT_TYPE = { date: 'YYYY-MM-DD', time: 'HH:mm:ss', dateTime: 'YYYY-MM-DD HH:mm:ss', };

相关资料: - BEM规范:https://getbem.com/naming/