前言
我是一名从事低代码平台研发的前端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 filesplugins: ['vue'],// add your custom rules hererules: {// 禁止函数圆括号之前有一个空格'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 || 10return {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)
官网:道一云七巧 - 可视化、智能化、数字化应用构建
免费体验:道一云产品免费试用