最近在升级一个旧的项目,早前的项目由于没有使用 ts,改起来有点痛苦以及当时没有做好的规范约束,以至于不同人贡献的代码差别很大,看着着实痛苦。
规范的意义
1、保证代码的一致性,从根本上提高代码整体的可读性、可维护性、可复用性:保持一致的编码风格,能让其他人在维护代码时能快速上手,即使没有什么经验的开发同学也能保障其代码的交付质量,为后期维护提供更好的支持。
2、有效降低开发和沟通的成本,提升团队整体效率:大型项目最复杂的模块往往仅占整体的一小部分,更多的是相对简单的日常开发和维护。遵循统一的技术规范,能够有效的降低多人协作工程中的沟通成本。如果没有统一规范则出现混乱的局面,留下庞大的技术债。
技术栈统一
- 基础:Typescript + Tsx
- 框架:React + React Dom
- Css 处理器:Sass
- UI 框架:Ant Design / DongDesign
- 状态管理:React Redux + Rematch
- 构建工具:Vite
Eslint 规范
基准规则
不重复造轮子,基于 eslint:recommend 配置并改进
能够帮助发现代码错误的规则,全部开启
ESLint 规则
- 允许非大写开头的构造函数名 ( new-cap: 0 )
- 禁止使用 var ( no-var: 2 )
- 限制 console 使用,仅允许 warn 和 error ( no-console: [1, { allow: ['warn', 'error'] }] )
- 禁用内部声明 ( no-inner-declarations: 2 )
- 强制使用 const 声明不会被修改的变量 ( prefer-const: [2, { 'destructuring': 'all' }] )
- 禁止特定语法,如 debugger、label 和 with 语句
React 规则
- 允许组件没有 displayName
- 禁止 JSX 中的重复属性
- 禁止未定义的 JSX 元素
- 禁止直接修改 state
- 强制组件的 render 方法返回值
- 不要求 prop-types 验证
- 不要求导入 React (新版 React 不需要)
- 禁止在 componentDidUpdate 中使用 setState
- 鼓励使用无状态函数组件
- 强制执行 React Hooks 规则
Import 规则
- 禁止未使用的模块
- import 语句后需要空行
- 禁止使用 CommonJS 模块语法 (require),但允许条件性 require
- 禁止重复导入
Import 排序
- 强制对 imports 和 exports 进行排序
TypeScript 规则
- 不强制函数返回类型注解
- 允许变量在定义前使用
- 对未使用的变量发出警告
- 不强制模块边界类型
- 允许特定情况下的空函数
- 允许 this 别名
- 允许 require 语句
- 允许 @ts-comment 注释
Git 规范
commit日志基本规范
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
- feat: 新增 feature
- fix: 修复 bug
- docs: 仅仅修改了文档,比如 README, CHANGELOG, CONTRIBUTE等等
- style: 仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑
- refactor: 代码重构,没有加新功能或者修复 bug
- perf: 优化相关,比如提升性能、体验
- test: 测试用例,包括单元测试、集成测试等
- chore: 改变构建流程、或者增加依赖库、工具等
- revert: 回滚到上一个版本
分支规范
test 测试分支
master 主分支
release release 分支
feat/xxx 特性分支
hotfix/xxx 修复分支
extends:
- eslint:recommended
- plugin:react/recommended
- plugin:@typescript-eslint/eslint-recommended
- plugin:@typescript-eslint/recommended
- prettier
- plugin:import/errors
- plugin:import/warnings
- plugin:import/typescript
parser: '@typescript-eslint/parser'
env:
es6: true
node: true
plugins:
- react
- prettier
- react-hooks
- simple-import-sort
- unused-imports
- '@typescript-eslint'
parserOptions:
sourceType: module
ecmaFeatures:
jsx: true
rules:
prettier/prettier:
[
2,
{
singleQuote: true,
trailingComma: 'es5',
semi: false,
'endOfLine': 'auto',
},
]
# eslint
new-cap: 0
no-var: 2
no-console: [1, { allow: ['warn', 'error'] }]
no-unused-vars: 0
no-inner-declarations: 2
camelcase: 0
no-useless-escape: 0
no-prototype-builtins: 0
no-restricted-syntax:
- 2
- DebuggerStatement
- LabeledStatement
- WithStatement
require-atomic-updates: 0
prefer-rest-params: 0
prefer-const: [2, { 'destructuring': 'all' }]
prefer-spread: 0
# react
react/display-name: 0
react/jsx-no-duplicate-props: 2
react/jsx-no-undef: 2
react/no-deprecated: 0
react/no-direct-mutation-state: 2
react/no-render-return-value: 2
react/require-render-return: 2
react/jsx-uses-react: 1
react/jsx-uses-vars: 1
react/prop-types: 0
react/react-in-jsx-scope: 0
react/no-did-update-set-state: 2
react/no-redundant-should-component-update: 2
react/no-typos: 2
react/no-unused-prop-types: 2
react/no-unused-state: 2
react/prefer-stateless-function: [1, { ignorePureComponents: true }]
react/void-dom-elements-no-children: 2
react/jsx-boolean-value: 1
react/jsx-no-bind: [2, { ignoreRefs: true, allowArrowFunctions: true }]
react-hooks/rules-of-hooks: 2
react-hooks/exhaustive-deps: 1
react-perf/jsx-no-new-object-as-prop: 1
react-perf/jsx-no-new-array-as-prop: 1
# import
import/no-unused-modules: 2
import/newline-after-import: 2
import/no-commonjs: [2, { allowConditionalRequire: true }]
import/no-duplicates: 2
# simple-import-sort
simple-import-sort/imports: 2
simple-import-sort/exports: 2
# typescript
'@typescript-eslint/camelcase': 0
'@typescript-eslint/explicit-function-return-type': 0
'@typescript-eslint/no-use-before-define': 0
'@typescript-eslint/no-unused-vars': [1, { ignoreRestSiblings: true }]
'@typescript-eslint/explicit-module-boundary-types': 0
'@typescript-eslint/ban-types': 0
'@typescript-eslint/no-empty-function':
[1, { 'allow': ['private-constructors', 'protected-constructors'] }]
'@typescript-eslint/no-this-alias': 0
'@typescript-eslint/no-var-requires': 0
'@typescript-eslint/ban-ts-comment': 0
'@typescript-eslint/strict-boolean-expressions':
[1, { allowNullableBoolean: true }]
overrides:
- files:
- '*.js'
- '*.jsx'
rules:
import/no-commonjs: 0
- files:
- '*.ts'
- '*.tsx'
rules:
import/no-unresolved: 0
lodash/prefer-lodash-method: 0
vscode 配置
// Place your settings in this file to overwrite default and user settings.
{
"editor.tabSize": 2,
"editor.insertSpaces": true,
"search.exclude": {
},
"editor.formatOnSave": false,
"editor.renderControlCharacters": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"[less]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[css]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[yaml]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[html]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[json]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "vscode.json-language-features",
},
"[markdown]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[mdx]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"files.associations": {
"*.xml": "html",
"*.svg": "html",
},
"prettier.ignorePath": ".eslintignore",
"liveServer.settings.port": 5501,
"code-runner.executorMapByGlob": {
"*.test.*": "cd $dir && yarn test --files $fullFileName",
},
"typescript.tsdk": "node_modules/typescript/lib",
"search.followSymlinks": false,
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/tmp": true,
"**/bower_components": true,
// "**/node_modules": true,
// "**/dist": true
},
"files.watcherExclude": {
"**/.git/objects/**": true,
"**/.git/subtree-cache/**": true,
"**/node_modules/**": true,
"**/tmp/**": true,
"**/bower_components/**": true,
"**/dist/**": true
},
"makefile.configureOnOpen": false
}
tsconfig 配置
{
"compilerOptions": {
"typeRoots": ["./node_modules/@types", "./types"],
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react",
"outDir": "./dist",
"plugins": [
{
"name": "typescript-plugin-css-modules"
}
],
"paths": {
"~/*": ["./src/*"],
"@constant/*": ["./src/constant/*"]
}
},
"exclude": [
"build",
"node_modules"
],
"include": [
"src", "typing.d.ts"
]
}