前言
我是一名从事低代码平台研发的前端CV程序猿,有几十名像我一样的小伙伴协同研发。在长期的多人协作和滚动迭代中,不出意外,代码中会充斥各种“坏味道”,如代码风格不统一、扩展性和灵活性降低等问题。我们是如何解决这些问题的呢?今天介绍的法宝之一就是大名鼎鼎的ESLint。
ESlint是何方神圣?
ESLint 是一个用于 JavaScript 代码静态分析的工具,它可以帮助开发团队遵循一致的代码风格和最佳实践。ESLint 解决了以下几个痛点:
1.代码风格统一:在多人协作的项目中,不同的开发者可能有不同的编码风格习惯,导致代码风格不一致。ESLint 可以定义代码规则,并对代码进行检查和修复,以确保整个项目的代码风格保持一致,提高代码的可读性和可维护性。
2.发现潜在的错误和问题:ESLint 通过静态分析代码,可以发现潜在的错误、漏洞和常见的编码问题。例如,未声明的变量、不推荐使用的语法、潜在的逻辑错误等。通过及早发现这些问题,可以提高代码质量和可靠性。
3.支持最佳实践:ESLint 提供了一系列的规则,可以帮助开发者遵循最佳实践和行业标准。例如,强制使用严格模式、禁止使用已废弃的 API、强制使用代码块等。这些规则可以帮助开发者编写更健壮、可靠的代码。
4.可扩展性和灵活性:ESLint 具有高度的可配置性和可扩展性。开发者可以根据项目的需求,自定义规则和配置,以适应不同的开发环境和项目要求。此外,ESLint 还支持插件系统,可以集成其他工具或规则集,以满足特定的需求。
ESLint的安装使用
ESLint在以下情况下特别有用:
1.项目初始化: 在创建新项目时,使用ESLint 可以确保从一开始就保持良好的代码质量。
2.团队协作: 当多个开发者合作时,ESLint 有助于维持一致的代码风格,减少代码审查时的冲突。
3.持续集成: 将ESLint集成到持续集成工具中,可以确保每次提交都符合项目的代码规范。
1.拓展安装:vscode编辑器
在编辑器内安装ESLint插件,并且确保项目路径下.vscode/settings.json中 "editor.codeActionsOnSave"."source.fixAll.eslint" 已设置为true,具体效果为在vue或者js中没对齐js代码时,保存可以自动完成对齐格式化
2.拓展安装:非vscode工具
如idea,内部提供了根据eslint格式化的功能,在完成了编码后,可以自行使用该功能进行格式化,或者尝试设置为保存,即马上格式化
3.项目安装
npm i eslint -D
4.创建规则配置文件-.eslintrc.js
文章最后附带彩蛋!!!!
off/0:关闭规则
warn/1: 打开规则,并且作为一个警告(不影响exit code)
error/2:打开规则,并且作为一个错误(exit code将会是1)
// 也可通过 npx eslint --init 来自动化生成对应配置文件
// 根目录创建.eslintrc.js
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint'
},
env: {
browser: true
},
extends: ['plugin:vue/essential','standard'
],
// required to lint *.vue files
plugins: ['vue'],
// add your custom rules here
rules: {
// 禁止函数圆括号之前有一个空格
'space-before-function-paren': ['error', 'never'],
......
}
}
5.按需使用.eslintignore
// 根目录创建.eslintignore
/build/
/config/
/dist/
6.检查本地项目代码
// pacakage.json
"scripts": {
"lint": "eslint --ext .js,.vue src ..."
}
// 控制台
npm run lint
// 批量规范格式化的指令为npm run lintFix
// 如果当前需求修改内容较多,且此前没有进行过格式化的话,需要执行此指令进行全量格式化
7.异常处理
当前eslint会通过git提交前校验执行,即当使用git commit 命令时,【husk】就会自动调用eslint的全局检查(后面会出文章会讲解如何配置),出现问题时会抛出异常,此时需要各位开发者认真检查下报错信息,如果是规范问题的话,可以直接根据报错信息定位到对应的文件。原则上不允许随意使用跳过命令来躲避检查,如果有不知道如何解决的情况,请咨询你的导师,旨在通过完善编码规范和知识库来统一思想,并通过自动化手段来约束团队。
8.可参考的配置文件(实践中)
rules: {
// 关闭有关生成器 空格的规则
'generator-star-spacing': 'off',
// 关闭消除未使用的变量,函数和函数的参数
'no-unused-vars': 0,
// 不允许在对象文字中的键和冒号之间使用空格
'key-spacing': ['error', { beforeColon: false }],
// 括号内的空格限制
'array-bracket-spacing': ['error', 'never'],
### 正确示例
let a,
b
### 错误示例
let a, b
// 声明变量时强制换行
'one-var-declaration-per-line': ['error', 'always'],
// 禁止函数圆括号之前有一个空格
'space-before-function-paren': ['error', 'never'],
### 正确示例
function() {}
### 错误示例
function () {}
// 除了生产环境,允许debugger
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// 关闭禁用不必要的return await
'no-return-await': 0,
// 限制最大长度不超过140个字符
**目前注释的原因是:在设置限制行数140临近几个数字,项目运行时候会卡在70%进度不动。设置在145以后就没有问题,错误数不多**
// 'max-len': [
// 'error',
// {
// code: 140,
// ignoreComments: true, // 忽略所有拖尾注释和行内注释
// ignoreUrls: true, // 忽略含有链接的行
// ignoreStrings: true, // 不忽略字符串的行
// ignoreTemplateLiterals: true // 忽略模板字符串的行
// }
// ],
// 强制限制驼峰法
camelcase: ['error', { properties: 'never' }],
### 正确示例
let isExamle = ''
### 错误示例
let is_example = ''
// 关闭callback必须有参数,关闭回调报错(callback(true/false))
'standard/no-callback-literal': 'off',
// 允许-在字符串中出现了Control的字符
'vue/no-parsing-error': 'off',
// 关闭计算的属性括号内强制执行一致的间距
'standard/computed-property-even-spacing': 'off',
// 运算符放到后面
'operator-linebreak': [
'error',
'after',
{ overrides: { '?': 'before', ':': 'before' } }
],
### 正确示例
let a +
b +
c
### 错误示例
let a
+ b
+ c
// 逗号放在后面
'comma-style': ['error', 'last'],
### 正确示例
let a,
b,
c
### 错误示例
let a
,b
,c
// 要求条件语句需要大括号
curly: ['error', 'all'],
### 正确示例
if() {return}
### 错误示例
if() return
// 句末不出现分号
semi: ['error', 'never'],
// 行注释必须另起一行
// 'line-comment-position': ['error', { position: 'above' }],
### 正确示例
// 引入font-icon
@import '~@/assets/icon/iconfont';
### 错误示例
@import '~@/assets/icon/iconfont'; // 引入font-icon
// 要求在注释之前有一个空格
'spaced-comment': ['error', 'always'],
### 正确示例
// 引入font-icon
### 错误示例
//引入font-icon
// 强制单引号和反勾号
quotes: ['error', 'single', { allowTemplateLiterals: true }],
// 建议使用let代替var(由于是建议,用警告)
'no-var': ['warn'],
// $emit不校验kebab-case,自定义事件名使用短横线方式
'vue/custom-event-name-casing': 0,
### --->vue /对自定义事件名称强制使用特定的大小写:https://eslint.vuejs.org/rules/custom-event-name-casing.html
// prop类型不能被假定为构造函数
'vue/require-prop-type-constructor': 0,
### --->vue /要求prop类型是构造函数:
https://eslint.vuejs.org/rules/require-prop-type-constructor.html
// 对象字面值属性名称引号
'quote-props': 0,
// 不要在子组件内部改变 prop
'vue/no-mutating-props': 0,
### --->vue /无变异道具:
https://eslint.vuejs.org/rules/no-mutating-props.html#rule-details
// 不允许v-if和v-for一起用
'vue/no-use-v-if-with-v-for': 0,
### --->vue /禁止在同一元素上使用v-if和v-for:
https://eslint.vuejs.org/rules/no-use-v-if-with-v-for.html
// 强制 计算属性(computed)有return
'vue/return-in-computed-property': 0,
### --->vue /计算属性返回-->强制在计算属性中存在return语句:
https://eslint.vuejs.org/rules/return-in-computed-property.html
// 禁止直接使用 Object.prototypes 的内置属性
'no-prototype-builtins': 0,
### 正确示例
var hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar");
### 错误示例
let hasBarProperty = foo.hasOwnProperty("bar");
// 在promise使用async
'no-async-promise-executor': 0,
### --->禁止将异步功能用作Promise执行器:
https://cn.eslint.org/docs/rules/no-async-promise-executor
// 检查每个prop的默认值对于给定类型是否有效
'vue/require-valid-default-prop': 0,
### --->vue /要求有效默认属性:
https://eslint.vuejs.org/rules/require-valid-default-prop.html
// 禁用不必要的转义字符
'no-useless-escape': 0,
// 禁止使用未注册的组件
'vue/no-unused-components': 0,
### ---> Vue /没有使用的组件:https://eslint.vuejs.org/rules/no-unused-components.html
// 未使用的scope等var
'vue/no-unused-vars': 0,
### ---> Vue /没有使用过的变量:https://eslint.vuejs.org/rules/no-unused-vars.html
// 缩进代码 2
"vue/html-indent": 2,
// 强制每行的最大属性数,默认是 1
"vue/max-attributes-per-line": 2
// 执行完当前判断里的内容,立马执行紧跟着的下一个里面的内容 例如switch的 case 没有break,紧接着执行下一个case
'no-fallthrough': 'off',
// 禁止在强制数组方法的回调函数中要return, 因为大部分时候使用循环是执行其它操作
'array-callback-return': 0
}
团队编码规范
经过一段时间的实践,我们总结出了一套完整的编码规范,下面介绍与主题相关的部分实践。
HTML
● 属性名全小写,用中划线做分隔符
● 不要在自动闭合标签结尾处使用斜线(HTML5 规范 指出他们是可选的)
● 不要忽略可选的关闭标签
减少标签数量
● 在编写HTML代码时,需要尽量避免多余的父节点
● 例如:在书写vue的template不书写多余的div
● 很多时候,需要通过迭代和重构来使HTML变得更少
● 任何时候都要用尽量小的复杂度和尽量少的标签来解决问题
JavaScript
1.【强制】使用2个空格做为一个缩进层级
2.【强制】switch 下的 case 和 default 必须增加一个缩进层级。
3. 空格类部分已经由工程中eslint规则进行定义
如:二元操作符左右两边都必须存在一个空格
一元操作符与变量名间不允许存在空格
定义对象时,属性名和属性值之间的冒号后必须存在一个空格
运算符处换行时,运算符必须处于行末尾
注释符号//与注释内容之间必须存在一个空格
.......
4.在if / else / for / do / while语句中,{}内部语句必须另起一行
5.语句末尾一律省略分号
6.注释必须另起一行
7.【强制】函数/方法注释必须包含函数说明,有参数和返回值时必须使用注释标注,参考下面样例:
正例:
/**
* 函数描述 *
* @param {string} p1 参数1的说明
* @param {string} p2 参数2的说明,比较长
* 那就换行了.
* @param {number=} p3 参数3的说明(可选)
* @return {Object} 返回值描述
*/
function foo(p1, p2, p3) {
var p3 = p3 || 10
return {
p1: p1,
p2: p2,
p3: p3
}
}
8.【强制】使用类型严格的===进行条件判断
9.【建议】若条件表达式过长,建议使用定义布尔值变量代替
正例:
var condition1 = arr.indexOf(a) >= -1
var condition2 = b === 2 || c === 3
var condition3 = b !== 2 || d === 4
if (condition1 && condition2 && condition3) {
// …
}
反例:
if (arr.indexOf(a) >= -1 && (b === 2 || c === 3) && (b !== 2 || d === 4)) {
// …
}
常量命名
const CON_NUM = 10
let row = Math.ceil(num/CON_NUM)
变量命名
● 命名控件使用lowerCamelCase风格
● 标准变量:采用小写驼峰式命名,前缀应当是名词。(函数的名字前缀为动词,以此区分变量和函数)
● 'ID'在变量名中全大写
● 'URL'在变量名中全大写
● 'Android'在变量名中大写第一个字母
● 'iOS'在变量名中小写第一个,大写后两个字母
● 组件变量:大驼峰式命名法,首字母大写,例:TableComponent
● 常量:名称全大写,用下划线连接,例:MAX_COUNT
● 命名建议:尽量在变量名字中体现所属类型,如:length、count等表示数字类型;而包含name、title表示为字符串类型,布尔型变量使用is、has开头命名。
● 【强制】字符串类型变量必须使用单引号,不允许使用双引号
● 变量尽量即声明即用,而不是统一声明所有变量
● 在字符串中引用变量时,使用模板字符串代替使用+拼接。
函数命名
常规函数
● 小驼峰式命名法,前缀应当为动词,call或者是handle等。
● 事件函数:以on开头,例:onClick,onConfirm
(注:事件包括click事件,change事件,emit自定义事件,页面跳转事件)
命名建议:可使用常见动词约定如下:
**动词**
|
**含义**
|
**返回值**
|
can
|
判断是否可执行某个动作(权限)
|
函数返回一个布尔值。true:可执行;false:不可执行
|
has
|
判断是否含有某个值
|
函数返回一个布尔值。true:含有此值;false:不含有此值
|
is
|
判断是否为某个值
|
函数返回一个布尔值。true:为某个值;false:不为某个值
|
get
|
获取某个值
|
函数返回一个非布尔值
|
set
|
设置某个值
|
无返回值、返回是否设置成功或者返回链式对象
|
load
|
加载某些数据
|
无返回值或者返回是否加载完成的结果
|
类&构造函数
● 大驼峰式命名法,首字母大写,前缀为名称。
类的成员
● 公共属性和方法:跟变量和函数的命名一样
● 私有属性和方法:前缀为_(下划线),后面跟公共属性和方法一样的命名方式
注意事项:
1.【强制】不要在内置对象的原型上添加方法,如Array, Date
2.【强制】不要在循环内部声明函数
3.【强制】不要有空的代码块
CSS
【强制】class 必须单词全字母小写,单词间以 _ 分隔。
/* 正例 */
.sidebar {}
.sidebar_left {}
.sidebar_left_xx_xx {}
/* 反例 */
.Sidebar {}
.sidebarLeft {}
/* 正例 */
.foreign_dialog_footer {}
/* 反例 */
.footer {}
编码顺序【可使用stylelint】
[
// 布局属性
'display',
'overflow',
'visibility',
'scroll-behavior',
'scroll-snap-align',
// 布局属性:定位
'position',
'top',
'right',
'bottom',
'left',
'z-index',
// 布局属性:浮动
'float',
'clear',
// 布局属性:列表
'list-style',
'list-style-type',
'list-style-position',
'list-style-image',
// 布局属性:表格
'table-layout',
'border-collapse',
'border-spacing',
'caption-side',
'empty-cells',
// 布局属性:弹性
'flex-flow',
'flex-direction',
'flex-wrap',
'justify-content',
'align-content',
'align-items',
'align-self',
'flex',
'flex-grow',
'flex-shrink',
'flex-basis',
'order',
// 布局属性:多列
'columns',
'column-width',
'column-count',
'column-gap',
'column-rule',
'column-rule-width',
'column-rule-style',
'column-rule-color',
'column-span',
'column-fill',
'column-break-before',
'column-break-after',
'column-break-inside',
// 布局属性:格栅
'grid-columns',
'grid-rows',
// 尺寸属性
'box-sizing',
'width',
'min-width',
'max-width',
'height',
'min-height',
'max-height',
'margin',
'margin-left',
'margin-right',
'margin-top',
'margin-bottom',
'padding',
'padding-left',
'padding-right',
'padding-top',
'padding-bottom',
'border',
'border-width',
'border-style',
'border-color',
'border-colors',
'border-left',
'border-left-width',
'border-left-style',
'border-left-color',
'border-left-colors',
'border-right',
'border-right-width',
'border-right-style',
'border-right-color',
'border-right-colors',
'border-top',
'border-top-width',
'border-top-style',
'border-top-color',
'border-top-colors',
'border-bottom',
'border-bottom-width',
'border-bottom-style',
'border-bottom-color',
'border-bottom-colors',
'border-radius',
'border-top-left-radius',
'border-top-right-radius',
'border-bottom-left-radius',
'border-bottom-right-radius',
'border-image',
'border-image-source',
'border-image-slice',
'border-image-width',
'border-image-outset',
'border-image-repeat',
'overflow-x',
'overflow-y',
// 界面属性
'appearance',
'outline',
'outline-width',
'outline-style',
'outline-color',
'outline-offset',
'outline-radius',
'outline-radius-topleft',
'outline-radius-topright',
'outline-radius-bottomleft',
'outline-radius-bottomright',
'background',
'background-color',
'background-image',
'background-repeat',
'background-repeat-x',
'background-repeat-y',
'background-position',
'background-position-x',
'background-position-y',
'background-size',
'background-origin',
'background-clip',
'background-attachment',
'bakground-composite',
'mask',
'mask-mode',
'mask-image',
'mask-repeat',
'mask-repeat-x',
'mask-repeat-y',
'mask-position',
'mask-position-x',
'mask-position-y',
'mask-size',
'mask-origin',
'mask-clip',
'mask-attachment',
'mask-composite',
'mask-box-image',
'mask-box-image-source',
'mask-box-image-width',
'mask-box-image-outset',
'mask-box-image-repeat',
'mask-box-image-slice',
'box-shadow',
'box-reflect',
'backdrop-filter',
'mix-blend-mode',
'filter',
'opacity',
'object-fit',
'clip',
'clip-path',
'resize',
'zoom',
'cursor',
'pointer-events',
'touch-callout',
'user-modify',
'user-focus',
'user-input',
'user-select',
'user-drag',
// 文字属性
'font',
'font-family',
'font-style',
'font-stretch',
'font-weight',
'font-variant',
'font-size',
'font-size-adjust',
'line-height',
'line-clamp',
'vertical-align',
'direction',
'unicode-bidi',
'writing-mode',
'ime-mode',
'text-overflow',
'text-decoration',
'text-decoration-line',
'text-decoration-style',
'text-decoration-color',
'text-decoration-skip',
'text-underline-position',
'text-align',
'text-align-last',
'text-justify',
'text-indent',
'text-stroke',
'text-stroke-width',
'text-stroke-color',
'text-shadow',
'text-transform',
'text-size-adjust',
'src',
'color',
// 内容属性
'tab-size',
'overflow-wrap',
'word-wrap',
'word-break',
'word-spacing',
'letter-spacing',
'white-space',
'caret-color',
'quotes',
'content',
'content-visibility',
'counter-reset',
'counter-increment',
'page',
'page-break-before',
'page-break-after',
'page-break-inside',
// 交互属性
'will-change',
'perspective',
'perspective-origin',
'backface-visibility',
'transform',
'transform-origin',
'transform-style',
'transition',
'transition-property',
'transition-duration',
'transition-timing-function',
'transition-delay',
'animation',
'animation-name',
'animation-duration',
'animation-timing-function',
'animation-delay',
'animation-iteration-count',
'animation-direction',
'animation-play-state',
'animation-fill-mode',
// Webkit专有属性
'-webkit-overflow-scrolling',
'-webkit-text-fill-color',
'-webkit-tap-highlight-color'
]
&:deep(.el-input) {
width: 60px;
}
Vue
Template
参考<VUE风格指南>
关于$refs的使用
为了维护的方便,防止出现修改原先属性致出现全局属性的错误的情况, 编码过程中不允许出现使用$refs方式去引用变量的情况
// 组件A
export default {
data() {
return {
propTest: {}
}
},
methods: {
getPropTest() {
return this.propTest
}
}
}
// 组件B中
export default {
mounted() {
console.log(this.$refs.propTest) // X 不允许使用
console.log(this.$refs.getPropTest()) // √
}
}
小结
总结起来,使用 ESLint 的步骤包括安装 ESLint、初始化配置文件、运行 ESLint 来检查代码,并根据需要修复问题。通过配置和使用规则,你可以定制 ESLint 来确保项目中的代码始终保持一致和高质量,并遵循最佳实践。在持续集成和团队协作中,ESLint是一个不可或缺的工具,助力开发者提升代码的质量和效率。
作者介绍:
道一云,成立于2004年,是中国低代码领域的领导厂商、腾讯战略投资企业、腾讯生态核心合作伙伴。拥有自主知识产权管理软件产品百余项,涵盖数字化应用构建低代码平台-七巧、全场景智能业务分析BI-七析、千人千面、数智化办公企业级门户-七星以及30多款开箱即用的场景应用。
欢迎关注:
公众号:道一云低代码(do1info)
官网:道一云七巧 - 可视化、智能化、数字化应用构建
免费体验:道一云产品免费试用