基于 React 项目前端团队推行代码规范及代码质量,我就简单几步搞定

语言: CN / TW / HK

前言

大家好,我是虚竹。

2022年是我们的一次重大技术栈转型(尝试、试错、挑战、精进),发动组员学习React新技术,前端小组采用主流的前端框架基于React、React Native系列生态支撑公司各种项目或产品的开发,其中应用包括 WEB 端、APP 端、H5 移动端、桌面端。

在团队协作中,为避免低级 Bug、以及团队协作时不同代码风格对彼此造成的困扰与影响。如果项目中没有统一的规范就会导致代码风格的五花八门,不利于代码的阅读和维护。所以会预先制定编码规范,使用 Lint 工具或代码风格检测工具,则可以辅助编码规范执行,有效控制代码质量。EsLint 则是其中一个很好的工具。

对于 EsLint 你们应该比较了解,是用来校验代码规范的。Prettier 是用来统一代码风格,格式化代码的,支持 js、ts、css、less、scss、json、jsx。而且集成了 vscode、vim、webstorm、sublime text 插件。

如果你用的技术栈是 Vue.js,可以移步到我之前发的文章:https://juejin.cn/post/7068573328914513928

Eslint & Prettier 最佳 CP 组合

EsLint 安装与配置

安装 Eslint

bash npm install -D eslint

VSCode 插件市场搜索 Eslint,下载并安装

image.png

在项目根目录新增.eslintrc.js文件,加入如下配置:

js module.exports = { root: true, // 当前配置为根配置,将不再从上级文件夹查找配置 parser: "@babel/eslint-parser", // 采用 @babel/eslint-parser 作为语法解析器 parserOptions: { requireConfigFile: false, // 禁用未检测到babel配置文件 ecmaFeatures: { // 指定要使用其他那些语言对象 jsx: true // 启用jsx语法 }, ecmaVersion: 9, // 指定使用的ECMAScript版本(2015-6,、2016-7、2017-8、2018-9、2019-10) sourceType: "module", // 指定来源的类型,有两种script或module babelOptions: { presets: ["@babel/preset-react"] // 对react语法的转换 } }, env: { browser: true, // 设置为所需检查的代码是在浏览器环境运行的 es6: true, // 设置为所需检查的代码是nodejs环境运行的 node: true // 设置所需检查代码为es6语法书写 }, extends: ["plugin:prettier/recommended", "plugin:react/recommended", "prettier"], plugins: ["react", "prettier"], // ESLint 附带有大量的规则。你可以使用注释或配置文件修改你项目中要使用的规则。要改变一个规则设置,你必须将规则 ID 设置为下列值之一: // "off" 或 0 - 关闭规则 // "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出) // "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出) rules: { "prettier/prettier": 2, "no-console": process.env.NODE_ENV === "production" ? "error" : "off", // 只有开发环境可以使用console "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", // 只有开发环境可以使用debugger "accessor-pairs": 2, // 应同时设置setter和getter "arrow-spacing": [2, { before: true, after: true }], // 箭头间距 "block-spacing": [2, "always"], // 块间距 "brace-style": [2, "1tbs", { allowSingleLine: true }], // 大括号样式允许单行 camelcase: [1, { properties: "always" }], // 为属性强制执行驼峰命名 "comma-dangle": [2, "never"], // 逗号不使用悬挂 "comma-spacing": [2, { before: false, after: true }], // 逗号间距 "comma-style": [2, "last"], // (默认)与数组元素,对象属性或变量声明在同一行之后和同一行需要逗号 "constructor-super": 2, "consistent-this": [2, "that"], // 强制this别名为that curly: [2, "multi-line"], // 当一个块只包含一条语句时,不允许省略花括号。 "dot-location": [2, "property"], // 成员表达式中的点应与属性部分位于同一行 "eol-last": 2, // 强制文件以换行符结束(LF) eqeqeq: ["error", "always", { null: "ignore" }], // 强制使用全等 "generator-star-spacing": [2, { before: true, after: true }], // 生成器中'*'两侧都要有间距 "global-require": 1, // 所有调用require()都位于模块的顶层 indent: ["off", 2, { SwitchCase: 2 }], // 缩进风格 "key-spacing": [2, { beforeColon: false, afterColon: true }], // 强制在对象字面量属性中的键和值之间保持一致的间距 "keyword-spacing": [2, { before: true, after: true }], // 关键字如if/function等的间距 "new-cap": [2, { newIsCap: true, capIsNew: false }], // 允许在没有new操作符的情况下调用大写启动的函数;(默认)要求new使用大写启动函数调用所有操作符 "new-parens": 2, // JavaScript通过new关键字调用函数时允许省略括号 "no-array-constructor": 1, // 不允许使用Array构造函数。除非要指定生成数组的长度 "no-class-assign": 2, // 不允许修改类声明的变量 "no-const-assign": 2, // 不能修改使用const关键字声明的变量 "no-control-regex": 0, // 不允许正则表达式中的控制字符 "no-delete-var": 2, // 不允许在变量上使用delete操作符 "no-dupe-args": 2, // 不允许在函数声明或表达式中使用重复的参数名称 "no-dupe-class-members": 2, // 不允许在类成员中使用重复的参数名称 "no-dupe-keys": 2, // 不允许在对象文字中使用重复键 "no-duplicate-case": 2, // 不允许在switch语句的case子句中使用重复的测试表达式 "no-empty-character-class": 2, // 不允许在正则表达式中使用空字符类 "no-empty-pattern": 2, // 不允许空块语句 "no-eval": 2, // 不允许使用eval "no-ex-assign": 2, // 不允许在catch子句中重新分配例外 "no-extend-native": 2, // 不允许直接修改内建对象的原型 "no-extra-boolean-cast": 2, // 禁止不必要的布尔转换 "no-extra-parens": [2, "functions"], // 仅在函数表达式附近禁止不必要的括号 "no-fallthrough": 2, // 消除一个案件无意中掉到另一个案件 "no-floating-decimal": 2, // 消除浮点小数点,并在数值有小数点但在其之前或之后缺少数字时发出警告 "no-func-assign": 2, // 允许重新分配function声明 "no-implied-eval": 2, // 消除隐含eval() "no-inner-declarations": [2, "functions"], // 不允许function嵌套块中的声明 "no-invalid-regexp": 2, // 不允许RegExp构造函数中的无效正则表达式字符串 "no-irregular-whitespace": 2, // 捕获无效的空格 "no-iterator": 2, // 消除阴影变量声明 "no-label-var": 2, // 禁止创建与范围内的变量共享名称的标签 "no-labels": [2, { allowLoop: false, allowSwitch: false }], // 消除 JavaScript 中使用带标签的语句 "no-lone-blocks": 2, // 消除脚本顶层或其他块中不必要的和可能混淆的块 "no-mixed-spaces-and-tabs": 2, // 不允许使用混合空格和制表符进行缩进 "no-multi-spaces": 2, // 禁止在逻辑表达式,条件表达式,声明,数组元素,对象属性,序列和函数参数周围使用多个空格 "no-multi-str": 2, // 防止使用多行字符串 "no-multiple-empty-lines": [2, { max: 1 }], // 最多一个空行 "no-native-reassign": 2, // 不允许修改只读全局变量 "no-new-object": 2, // 不允许使用Object构造函数 "no-new-require": 2, // 消除new require表达的使用 "no-new-symbol": 2, // 防止Symbol与new 同时使用的意外错误 "no-new-wrappers": 2, // 杜绝使用String,Number以及Boolean与new操作 "no-obj-calls": 2, // 不允许调用Math,JSON和Reflect对象作为功能 "no-octal": 2, // 不允许使用八进制文字 "no-octal-escape": 2, // 不允许字符串文字中的八进制转义序列 "no-path-concat": 2, // 防止 Node.js 中的目录路径字符串连接无效 "no-redeclare": 2, // 消除在同一范围内具有多个声明的变量 "no-regex-spaces": 2, // 在正则表达式文字中不允许有多个空格 "no-return-assign": [2, "except-parens"], // 消除return陈述中的任务,除非用括号括起来 "no-self-assign": 2, // 消除自我分配 "no-self-compare": 2, // 禁止比较变量与自身 "no-sequences": 2, // 禁止使用逗号运算符 "no-sparse-arrays": 2, // 不允许稀疏数组文字 "no-this-before-super": 2, // 在呼call前标记this/super关键字super() "no-throw-literal": 2, // 不允许抛出不可能是Error对象的文字和其他表达式 "no-trailing-spaces": 2, // 不允许在行尾添加尾随空白 "no-undef": 2, // 任何对未声明的变量的引用都会导致错误 "no-undef-init": 2, // 消除初始化为undefined的变量声明 "no-underscore-dangle": 2, // 标识符不能以_开头或结尾 "no-unexpected-multiline": 2, // 不允许混淆多行表达式 "no-unmodified-loop-condition": 2, // 查找循环条件内的引用,然后检查这些引用的变量是否在循环中被修改 "no-unneeded-ternary": [2, { defaultAssignment: false }], // 不允许将条件表达式作为默认的分配模式 "no-unreachable": 2, // 不允许可达代码return,throw,continue,和break语句后面还有语句。 "no-unsafe-finally": 2, // 不允许return,throw,break,和continue里面的语句finally块 "no-unused-vars": [1, { vars: "all", args: "after-used" }], // 消除未使用的变量,函数和函数的参数 // vars: 'all' 检查所有变量的使用情况,包括全局范围内的变量。这是默认设置。 args: 'after-used' 只有最后一个参数必须使用。例如,这允许您为函数使用两个命名参数,并且只要您使用第二个参数,ESLint 就不会警告您第一个参数。这是默认设置。 "no-useless-call": 2, // 标记使用情况,Function.prototype.call()并且Function.prototype.apply()可以用正常的函数调用来替代 "no-useless-computed-key": 2, // 禁止不必要地使用计算属性键 "no-useless-constructor": 2, // 在不改变类的工作方式的情况下安全地移除的类构造函数 "no-useless-escape": 0, // 禁用不必要的转义字符 "no-whitespace-before-property": 2, // 如果对象的属性位于同一行上,不允许围绕点或在开头括号之前留出空白 "no-with": 2, // 禁用with "no-var": 2, // 禁用var "one-var": [2, { initialized: "never" }], // 强制将变量声明为每个函数(对于var)或块(对于let和const)范围一起声明或单独声明。 initialized: 'never' 每个作用域要求多个变量声明用于初始化变量 "operator-linebreak": [2, "after", { overrides: { "?": "before", ":": "before" } }], // 实施一致的换行 "padded-blocks": [2, "never"], // 在块内强制执行一致的空行填充 "prefer-destructuring": ["error", { object: false, array: false }], // 此规则强制使用解构而不是通过成员表达式访问属性。 quotes: [2, "double", { avoidEscape: true, allowTemplateLiterals: true }], // avoidEscape: true 允许字符串使用单引号或双引号,只要字符串包含必须以其他方式转义的引号 ;"allowTemplateLiterals": true 允许字符串使用反引号 radix: 2, // parseInt必须指定第二个参数 semi: [0, "never"], // 不使用分号 "semi-spacing": [2, { before: false, after: true }], // 强制分号间隔 "space-before-blocks": [2, "always"], // 块必须至少有一个先前的空间 "space-before-function-paren": [ 2, { anonymous: "always", // 用于匿名函数表达式(例如function () {}) named: "never", // 用于命名函数表达式(例如function foo () {}) asyncArrow: "always" // 用于异步箭头函数表达式(例如async () => {}) } ], "space-in-parens": [2, "never"], // 禁止或要求(或)左边的一个或多个空格 "space-infix-ops": 2, // 强制二元运算符左右各有一个空格 "space-unary-ops": [2, { words: true, nonwords: false }], // words: true 如:new,delete,typeof,void,yield 左右必须有空格 // nonwords: false 一元运算符,如:-,+,--,++,!,!!左右不能有空格 "spaced-comment": [2, "always", { markers: ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], // 注释开始后,此规则将强制间距的一致性//或/* "template-curly-spacing": [2, "never"], // 不允许大括号内的空格 "use-isnan": 2, // 禁止比较时使用NaN,只能用isNaN() "valid-typeof": 2, // 必须使用合法的typeof的值 "wrap-iife": [2, "any"], // 立即执行函数表达式的小括号风格 "yield-star-spacing": [2, "both"], // 强制执行*周围 yield*表达式的间距,两侧都必须有空格 yoda: [2, "never"], "prefer-const": 2, // 使用let关键字声明的变量,但在初始分配后从未重新分配变量,应改为const声明 "object-curly-spacing": [2, "always", { objectsInObjects: true }], // 不允许以对象元素开始和/或以对象元素结尾的对象的大括号内的间距 "array-bracket-spacing": [2, "never"], // 不允许数组括号内的空格 "react/jsx-uses-react": 1, // 防止React被错误地标记为未使用 "react/jsx-uses-vars": 2, // 防止在JSX中使用的变量被错误地标记为未使用 "react/react-in-jsx-scope": 0, // 关闭使用JSX时防止丢失React "react/prop-types": 0 // 关闭React组件中的props验证 } };

Prettier 安装与配置

安装 Prettier

js npm install -D prettier

VSCode 插件市场搜索 Prettier,下载并安装

image.png

在项目根目录新增.prettierrc.js文件,加入如下配置:

js module.exports = { printWidth: 175, // 每行代码长度(默认175) trailingComma: "none", // 在对象或数组最后一个元素后面是否加逗号 tabWidth: 2, // 每个tab相当于多少个空格(默认2) useTabs: true, // 使用tab(制表符)缩进而非空格 semi: true, // 是否在行尾加分号 singleQuote: false, // 使用单引号代替双引号 arrowParens: "avoid", // (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 bracketSpacing: true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }" proseWrap: "preserve", // 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行 htmlWhitespaceSensitivity: "ignore", // HTML 文件空格敏感度 jsxSingleQuote: false, // jsx中是否使用单引号 endOfLine: "auto", // 结尾是 \n \r \n\r auto jsxBracketSameLine: true // 将>多行JSX元素放在最后一行的末尾,而不是单独放在下一行 };

忽略校验的文件

在项目根目录新建.eslintignore & .prettierignore两个文件,加入如下配置:

bash node_modules build src/statics public README.md package.json yarn.lock

VSCode 编辑器配置

方法一

直接打开vscode编辑器,选择顶部菜单栏:文件->首选项->设置,如下图所示:

image.png

image.png

image.png

方法二

在项目根目录新建 .vscode 文件夹,再新增 settings.json 文件,加入如下配置:

json { "editor.tabSize": 2, "editor.formatOnSave": true, // 保存时自动格式化 "editor.wordWrap": "on", // 是否自动换行 /* eslint的配置 */ "eslint.enable": true, "eslint.run": "onSave", // 保存后 "eslint.validate": ["javascript", "javascriptreact", "vue", "html"], "editor.codeActionsOnSave": { "source.fixAll": true }, "eslint.alwaysShowStatus": true, "javascript.validate.enable": false, "html.validate.scripts": false, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[vue]": { "editor.defaultFormatter": "octref.vetur" }, "[less]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[scss]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[html]": { "editor.defaultFormatter": "vscode.html-language-features" }, // 文件头部注释 "fileheader.customMade": { "description": "描述信息", "author": "Jack Chen @懒人码农", "Date": "Do not edit", "LastEditors": "Jack Chen", "LastEditTime": "Do not Edit" }, // 函数注释 "fileheader.cursorMode": { "desc": "描述信息", "param": "", "return": "" }, // 配置md和json文件不自动添加头部注释 "fileheader.configObj": { "prohibitAutoAdd": ["json", "md"] }, "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.suggestSelection": "first", "vsintellicode.modify.editor.suggestSelection": "automaticallyOverrodeDefaultValue", "files.exclude": { "**/.classpath": true, "**/.project": true, "**/.settings": true, "**/.factorypath": true }, "emmet.includeLanguages": { "javascript": "javascriptreact" }, "emmet.triggerExpansionOnTab": true

在项目开发中,加入 @babel/eslint-parser @babel/preset-react eslint eslint-config-babel eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-react prettier 这些依赖库

js npm install -D @babel/eslint-parser @babel/preset-react eslint eslint-config-babel eslint-config-prettier eslint-plugin-import eslint-plugin-prettier eslint-plugin-react prettier

开发时,保存文件,即可按 prettier 规则格式化文件,并自动修复可修复的 issue,不能自动修复的,请根据提示,手动修复。

提示:vscode 已设置保存时格式化,但有时并不会格式化文件。已保存的文件还存在报错的,请手动格式化,并修改相应问题后,再次保存。

如果出现报错如下图所示:

image.png

处理方式:

js // 先卸载 npm uninstall -D @baebel/eslint-parser // 再安装 npm install -D @baebel/eslint-parser

再重启 VSCode 编辑器,运行,报错消失

提交时校验(git commit)

安装配置 husky v7.0.4

bash npm install -D husky

package.json文件中添加如下代码:

json { "scripts": { "prepare": "husky install" } }

执行命令生成.husky目录,代码如下:

js npm run prepare

添加另一个钩子commit-msg可以用来规范化标准格式,并且可以按需指定是否要拒绝本次提交,命令如下:

js npx husky add .husky/commit-msg

.husky目录下,会生成commit-msg hook文件,打开后添加如下代码:

```bash

!/bin/sh

. "$(dirname "$0")/_/husky.sh"

npx --no-install commitlint --edit $1 ```

安装配置 commitlint

自定义 GIT 提交规范,命令如下:

bash npm install @commitlint/cli @commitlint/config-conventional -D

在项目根目录创建commitlint.config.js文件,添加代码如下:

js module.exports = { // 继承的规则 extends: ['@commitlint/config-conventional'], // 定义规则类型 rules: { // type 类型定义,表示 git 提交的 type 必须在以下类型范围内 'type-enum': [ 2, 'always', [ 'feat', // 新功能 feature 'fix', // 修复 bug 'docs', // 文档注释 'style', // 代码格式(不影响代码运行的变动) 'refactor', // 重构(既不增加新功能,也不是修复bug) 'perf', // 性能优化 'test', // 增加测试 'chore', // 构建过程或辅助工具的变动 'revert', // 回退 'build' // 打包 ] ], // subject 大小写不做校验 'subject-case': [0] } }

添加另一个钩子pre-commit hook会在提交前被调用,并且可以按需指定是否要拒绝本次提交,命令如下:

bash npx husky add .husky/pre-commit "npx --no-install lint-staged --allow-empty"

.husky目录下,会生成pre-commit文件,打开后添加如下代码:

```js

!/bin/sh

. "$(dirname "$0")/_/husky.sh"

这里就是唤醒lint-staged

npx --no-install lint-staged --allow-empty ```

安装配置 lint-staged

bash npm install -D lint-staged

package.json文件中,添加如下代码:

json { "lint-staged": { "*.{js,jsx,ts,tsx}": [ "eslint --fix", "prettier --write" ] } }

常用 GIT HOOKS

image.png

提交规范

git commit -m <type>[optional scope]: <description>(注意冒号后面有空格)

  • type:用于表明我们这次提交的改动类型,是新增了功能?还是修改了测试代码?又或者是更新了文档?
  • optional scope:一个可选的修改范围。用于标识此次提交主要涉及到代码中哪个模块。
  • description:一句话描述此次提交的主要内容,做到言简意赅。

示例代码如下:

```bash git add . git commit -m "feat: 增加 xxx 功能"

或者

git commit -m "bug: 修复 xxx 功能" ```

到这里已大功告成,大家可以自行尝试,如有问题,请留言讨论。

create-react-app 兼容 IE11 配置

安装react-app-polyfill插件,代码如下:

bash npm install -S react-app-polyfill

安装成功后,在src文件夹下的index.js文件首行加入代码如下图所示:

image.png

还没结束,需要在package.json文件中加入browserslist,如下图所示:

image.png

最后重新运行项目,如果发现还是空白但是浏览器没有报错,把node_moudles删掉重新安装运行即可。

结语

好了,就写到这,简单几步搞定,不需要花太长时间。如果你对自己的代码有严格的代码洁癖或者你对自己的前端团队有必要的代码书写规范,建议赶快用起来。

文中如有错误,欢迎在评论区留言指正,如果这篇文章有帮到你了,还望点个赞以表鼓励。