一、项目工程化
1.1初始环境
- node v18.17.0
- pnpm 8.15.5
- vite 5.2.0
1.1.1安装pnpm
pnpm:performant npm ,意味“高性能的 npm”。pnpm由npm/yarn衍生而来,解决了npm/yarn内部潜在的bug,极大的优化了性能,扩展了使用场景。被誉为“最先进的包管理工具”
pnpm安装指令
npm i -g pnpm
1.1.2搭建脚手架
pnpm create vite
进入到项目根目录pnpm i安装全部依赖.
2.2项目配置
一、配置eslint
eslint中文官网:eslint.nodejs.cn/
ESLint最初是由Nicholas C. Zakas 于2013年6月创建的开源项目。它的目标是提供一个插件化的javascript代码检测工具
vite@4.4.5自带.eslintrc.cjs (以下是默认内容, 之后要被全部替换)
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
}
1.1 react 环境代码校验插件说明(目前共14个)
###### 让所有与prettier规则存在冲突的Eslint rules失效,并使用prettier进行代码检查
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-node": "^11.1.0",
###### 运行更漂亮的Eslint,使prettier规则优先级更高,Eslint优先级低
"eslint-plugin-prettier": "^4.2.1",
###### 该解析器允许使用Eslint校验所有babel code
"@babel/eslint-parser": "^7.19.1",
###############################################
"@typescript-eslint/eslint-plugin": "^6.1.0", //typescript对应的eslint 规则插件
"@typescript-eslint/parser": "^6.1.0",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.33.0",
"prettier": "^3.0.0"
###### import 排序功能
"eslint-plugin-import"
"@typescript-eslint/parser"
"eslint-import-resolver-typescript"
"eslint-import-resolver-alias"
安装指令( 先装11个,再装3个 )
pnpm i eslint eslint-plugin-import eslint-plugin-react eslint-plugin-node @babel/eslint-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-import @typescript-eslint/parser eslint-import-resolver-typescript eslint-import-resolver-alias -D
1.2修改.eslintrc.cjs配置文件(全选覆盖)
module.exports = {
// 运行环境
env: {
browser: true,
es2021: true,
node: true,
jest: true,
},
/* 指定如何解析语法 */
/** 优先级低于 parse 的语法解析配置 */
parserOptions: {
parser: '@typescript-eslint/parser', // 指定解析器
ecmaVersion: 'latest', // 允许解析那个版本的特性
sourceType: 'module', // 允许使用 import
jsxPragma: 'React',
ecmaFeatures: {
jsx: true, // 允许对JSX进行解析
},
},
/* 继承已有的规则 */
extends: [
'eslint:recommended', // vite自带
'plugin:@typescript-eslint/recommended', // vite自带 @typescript-eslint/eslint-plugin的推荐规则
'plugin:react-hooks/recommended', // vite自带
'plugin:import/typescript', // eslint-plugin-import 抛出导入等支持的规则
'plugin:prettier/recommended', // eslint-plugin-prettier 的推荐规则
'plugin:import/recommended', // import排序
],
// https://stackoverflow.com/questions/64126764/how-do-i-fix-typescript-compiler-errors-on-css-files
ignorePatterns: ['**/*.css', '**/*.scss'],
parser: '@typescript-eslint/parser', // 指定解析器
plugins: ['react-refresh', '@typescript-eslint', 'react', 'prettier'],
/*
* "off" 或 0 - 关闭规则
* "warn" 或 1 - 打开规则作为警告(不影响退出代码)
* "error" 或 2 - 打开规则作为错误(退出代码将为 1)
*/
rules: {
// react
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'react/no-children-prop': 'off',
'react-refresh/only-export-components': 'off',
// eslint(https://eslint.bootcss.com/docs/rules/)
'no-var': 'error', // 要求使用 let 或 const 而不是 var
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-unexpected-multiline': 'error', // 禁止空余的多行
'no-useless-escape': 'off', // 禁止不必要的转义字符
'prettier/prettier': ['error', { endOfLine: 'auto' }],
// typeScript (https://typescript-eslint.io/rules)
'@typescript-eslint/no-unused-vars': 'off', // 禁止定义未使用的变量
'@typescript-eslint/prefer-ts-expect-error': 'error', // 禁止使用 @ts-ignore
'@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。
'@typescript-eslint/semi': 'off',
// import order (https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md)
'import/default': 'off',
'import/no-named-as-default': 'off',
'import/no-named-as-default-member': 'off',
'import/order': [
'error',
{
// 对导入模块进行分组,分组排序规则如下
groups: [
'type', // 类型文件
'builtin', // 内置模块
'external', // 外部模块
'internal', // 内部引用
'parent', // 父节点依赖
'sibling', // 兄弟依赖
'index', // index文件
'object',
'unknown',
],
//通过路径自定义分组
pathGroups: [
{
group: 'type',
pattern: 'react',
position: 'before',
},
{
group: 'type',
pattern: 'react-router-dom',
},
{
group: 'type',
pattern: '@/types/**',
position: 'after',
},
{
group: 'external',
pattern: 'react',
position: 'before',
},
{
group: 'external',
pattern: 'react-router-dom',
position: 'before',
},
{
group: 'internal',
pattern: '@/**', // 把@开头的应用放在当前分组后面
},
],
pathGroupsExcludedImportTypes: ['react', 'react-router-dom'],
distinctGroup: true, // 是否开启独特组,用于区分自定义规则分组和其他规则分组
'newlines-between': 'never', // 每个分组之间换行
alphabetize: { order: 'asc' }, // 相同分组排列规则 按字母升序排序
},
],
},
settings: {
'import/resolver': {
typescript: true,
node: true,
alias: {
map: [['', './public']],
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
},
}
1.3创建.eslintignore忽略文件
build
dist
node_modules
1.4配置脚本
package.json新增两个运行脚本
"scripts": {
...
"fix": "eslint src --fix",
},
通过pnpm run lint去检测语法,如果出现不规范格式,通过pnpm run fix 修改
二、配置prettier
有了eslint,为什么还要有prettier?eslint针对的是javascript,他是一个检测工具,包含js语法以及少部分格式问题,在eslint看来,语法对了就能保证代码正常运行,格式问题属于其次;
而prettier属于格式化工具,它看不惯格式不统一,所以它就把eslint没干好的事接着干,另外,prettier支持
包含js在内的多种语言。
总结起来,eslint和prettier这俩兄弟一个保证js代码质量,一个保证代码美观。
2.1安装依赖包
先装7个,再装3个
pnpm i prettier eslint-plugin-prettier eslint-config-prettier -D
2.2创建.prettierrc.cjs, 添加规则
// https://prettier.io/docs/en/options.html
module.exports = {
printWidth: 100, //单行长度
tabWidth: 2, //缩进长度
useTabs: false, //使用空格代替tab缩进
semi: false, //句末分号
singleQuote: true, //js中使用单引号
jsxSingleQuote: false, // jsx中使用双引号
quoteProps: 'as-needed', //仅在必需时为对象的key添加引号
trailingComma: 'all', //多行时尽可能打印尾随逗号
bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar }
bracketSameLine: false, //多属性html标签的‘>’折行放置
arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x
requirePragma: false, //无需顶部注释即可格式化
insertPragma: false, //在已被preitter格式化的文件顶部加上标注
proseWrap: 'preserve', //什么都不做,让散文保持原样
htmlWhitespaceSensitivity: 'ignore', //对HTML全局空白不敏感
vueIndentScriptAndStyle: true, //不对vue中的script及style标签缩进
endOfLine: 'lf', //结束行形式
embeddedLanguageFormatting: 'auto', //对引用代码进行格式化
singleAttributePerLine: false, //在 HTML、Vue 和 JSX 中强制每行使用单一属性
}
2.3创建.prettierignore忽略文件
/.git
/.vscode
/build
/dist/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*
2.4配置脚本
"scripts": {
...
"format": "prettier --write \"./src/**/*.{tsx,ts,jsx,js,json,html,md}\"",
}
代码格式化 pnpm run format
CTRL + SHIFT + P输入reload window重启VsCode生效
三、配置husky
在上面我们已经集成好了我们代码校验工具,但是需要每次手动的去执行命令才会格式化我们的代码。如果有人没有格式化就提交了远程仓库中,那这个规范就没什么用。所以我们需要强制让开发人员按照代码规范来提交。
要做到这件事情,就需要利用husky在代码提交之前触发git hook(git在客户端的钩子),然后执行 pnpm run format来自动的格式化我们的代码。
安装 husky
pnpm install -D husky@8.0.3
查看包版本
npm view husky versions
执行
pnpm exec husky init
会在根目录下生成个一个.husky目录,在这个目录下面会有一个pre-commit文件,这个文件里面的命令在我们执行commit的时候就会执行
尝试在.husky/pre-commit文件添加如下命令:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm fix
pnpm format
git add .
.husky/pre-commit文件在之后配置 lint-staged 时还要再次被修改! 在这里写是为了理解文件可以写一些什么代码, 方便理解.
当我们对代码进行commit操作的时候,就会执行命令,对代码进行格式化,然后再提交。
四、配置cz-customizable
修改 package,json 中的 "type": "commonjs",
pnpm i -D cz-customizable
根目录创建.cz-config.js
module.exports = {
types: [
{
value: '✨ feat',
name: '✨ feat: 新功能',
},
{
value: '🐛 fix',
name: '🐛 fix: 修复bug',
},
{
value: '🚀 chore',
name: '🚀 chore: 构建/工程依赖/工具',
},
{
value: '💄 style',
name: '💄 style: 代码的样式美化',
},
{
value: '♻️ refactor',
name: '♻️ refactor: 重构',
},
{
value: '⚡️ perf',
name: '⚡️ perf: 性能优化',
},
{
value: '✅ test',
name: '✅ test: 测试',
},
{
value: '🎉 init',
name: '🎉 init: 初始化',
},
{
value: '📝 docs',
name: '📝 docs: 文档变更',
},
{
value: '⏪️ revert',
name: '⏪️ revert: 回退',
},
{
value: '🔧 config',
name: '🔧 config: 修改配置',
},
{
value: '📦️ build',
name: '📦️ build: 打包',
},
{
value: '👷 ci',
name: '👷 ci: CI related changes',
},
],
messages: {
type: '请选择提交类型(必填)',
customScope: '请输入文件修改范围(可选)',
subject: '请简要描述提交(必填)',
body: '请输入详细描述(可选)',
breaking: '列出任何BREAKING CHANGES(可选)',
footer: '请输入要关闭的issue(可选)',
confirmCommit: '确定提交此说明吗?',
},
allowCustomScopes: true,
allowBreakingChanges: [':sparkles: feat', ':bug: fix'],
subjectLimit: 72,
}
配置脚本, 注意修改"type": "commonjs"
"type": "commonjs",
"scripts": {
...
"prepare": "husky install",
"commit": "cz-customizable"
},
以后使用 pnpm commit 代替 git commit -m
但这样做还是会有人使用 git commit,所以要配置 commitlint
五、配置commitlint
git log 日志规范
5.1安装规范包
pnpm add @commitlint/cli commitlint-config-cz commitlint-config-git-commit-emoji -D
5.2创建commitlint.config.cjs(注意是cjs),然后添加下面的代码:
module.exports = {
extends: ['git-commit-emoji', 'cz']
}
5.3在 package.json中配置scripts命令
# 在scrips中添加下面的代码
"scripts": {
...
"commitlint": "commitlint --config commitlint.config.cjs -e -V"
},
5.4配置husky
npx husky add .husky/commit-msg
在生成的commit-msg文件中添加下面的命令
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm run commitlint
这样git commit -m就提交不了了,必须用pnpm commit提交!
六、配置 lint-staged
pnpm i lint-staged -D
package.json文件
"scripts": {
...
"lint-staged": "lint-staged"
},
"lint-staged": {
"./src/**/*.{html,tsx,ts,jsx,js,json,vue}": [
"eslint src --fix",
"prettier --write \"./src/**/*.{tsx,ts,jsx,js,json,html,md}\""
]
},
.husky\pre-commit 文件内容如下:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm run lint-staged
lint-staged 的作用是只校验 git add 中的文件
没有被 git add 的文件不校验
七、强制使用pnpm包管理器工具
团队开发项目的时候,需要统一包管理器工具,因为不同包管理器工具下载同一个依赖,可能版本不一样,
导致项目出现bug问题,因此包管理器工具需要统一管理!!!
在根目录创建 scripts/preinstall.js文件,添加下面的内容
if (!/pnpm/.test(process.env.npm_execpath || '')) {
console.warn(
`\u001b[33mThis repository must using pnpm as the package manager ` +
` for scripts to work properly.\u001b[39m\n`,
)
process.exit(1)
}
package.json配置命令
"scripts": {
...
"preinstall": "node ./scripts/preinstall.js"
},
当我们使用npm或者yarn来安装包的时候,就会报错了。原理就是在install的时候会触发preinstall(npm提供的生命周期钩子)这个文件里面的代码。
八、配置 @ 别名
安装 node ts 包
pnpm i @types/node -D
配置 vite.config.ts 进行语法上的解析
import { join } from 'node:path'
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
// 配置 @ 的路径别名
'@': join(__dirname, './src/'),
}
}
})
配置 tsconfig.json 进行编辑器的代码提示功能
{
"compilerOptions": {
...
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
},
...
}
vscode中执行Ctrl+Shift+P输入reload window重启vscode
如果不配置此项, 会发现vscode引入文件中有波浪线警告
二、项目最终配置
package.json
{
"name": "demo",
"private": true,
"version": "0.0.0",
"type": "commonjs",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview",
"fix": "eslint src --fix",
"format": "prettier --write \"./src/**/*.{tsx,ts,jsx,js,json,html,md}\"",
"prepare": "husky",
"commit": "cz-customizable",
"commitlint": "commitlint --config commitlint.config.cjs -e -V",
"lint-staged": "lint-staged",
"preinstall": "node ./scripts/preinstall.js"
},
"lint-staged": {
"./src/**/*.{html,tsx,ts,jsx,js,json,vue}": [
"eslint src --fix",
"prettier --write \"./src/**/*.{tsx,ts,jsx,js,json,html,md}\""
]
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.24.1",
"@commitlint/cli": "^19.2.1",
"@types/node": "^20.12.2",
"@types/react": "^18.2.66",
"@types/react-dom": "^18.2.22",
"@typescript-eslint/eslint-plugin": "^7.4.0",
"@typescript-eslint/parser": "^7.4.0",
"@vitejs/plugin-react-swc": "^3.5.0",
"commitlint-config-cz": "^0.13.3",
"commitlint-config-git-commit-emoji": "^1.0.0",
"cz-customizable": "^7.0.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.6",
"husky": "^8.0.3",
"lint-staged": "^15.2.2",
"prettier": "^3.2.5",
"typescript": "^5.2.2",
"vite": "^5.2.0"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Mine Custom Config */
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
三、react组件通信方式
组件通信方式很重要,不管是项目还是面试都是经常用到的知识点。
- props: 可以实现父子组件、子父组件、甚至兄弟组件通信(状态提升)
- context: 可以实现任意组件通信
- 集中式状态管理等: Redux、Mobox、Zustand、Resso 等...
- 消息订阅-发布: pubsub 等...