前言
不知道大家平时是怎么搭建项目的,工作时一般都是使用公司脚手架搭建项目。 私底下想做点东西我常用由creat-react-app或者vite创建一个项目,然后装一大堆的配置。 为了更好的摸鱼🐟,我打算建一个标准化的React项目。
这篇文章就是搭建的过程,命令行操作单独列出来了,CV即可
新建一个文件夹,开始项目搭建
bash
mkdir template-ts-react
cd template-ts-react
包管理工具
项目采用pnpm作为包管理工具,其速度快,而且节省磁盘空间
bash
pnpm init
(可选) 将pnpm切换成tabao镜像
bash
# 查看pnpm源
pnpm get registry
# 切换tabao镜像
pnpm config set registry https://registry.npmmirror.com
pnpm install
# 还原
pnpm config set registry https://registry.npmjs.org
这样设置源也有一些问题,自己用用到还算好,但如果碰到日常开发使用公司的私有源就有点麻烦。
使用nrm切换能更方便些
bash
pnpm i -g nrm
nrm use taobao
# 添加私有源
nrm add <registry> <url>
nrm del <registry>
# 查看源
nrm ls

切换源之后pnpm全局安装会出现 ERR_PNPM_REGISTRIES_MISMATCH问题 按提示重新安装下好了
bash
pnpm i -g
pnpm i -g pnpm
vite
Vite原生支持ESM和Typescript,支持自动热更新,构建速度也比webpack快。除了陈年老项目实在想不出不用的理由。。。
bash
pnpm i vite -D
在项目根目录新建一个index.html
,为作为入口文件。
bash
# 会开启一个本地服务器,就可以直接打开这个index.html页面
pnpm vite
vite v1版本使用Koa开启本地服务器,v2及以上版本使用connect中间件的形式, live-server也是使用connect中间件的形式

根目录下新建下一个vite.config.ts,安装下@vitejs/plugin-react
,开启HMR特性
bash
pnpm i @vitejs/plugin-react -D
ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
base: './',
plugins: [react()],
})
引入React 和 Typescript
React核心代码位于React包中,虚拟DOM相关代码在React的Reconciler包中,使用虚拟DOM对接不同的宿主环境,调用对应的API,就能实现多平台的渲染能力。

在浏览器和Nodejs宿主环境使用ReactDOM。
bash
pnpm i react react-dom
这里以版本的形式列出了部分React的变化,标记红色粗体部分在后续的操作中会接触到
并发模式
在项目中使用React第一步就是需要先创建一个React root,用于在浏览器DOM中显示元素,React v16和v17版本创建的项目有些是使用ReactDOM.render(<App />, document.getElementById('root'));
,v18则全面开启并发模式(ConCurrent),实现并发更新
差异如下:

tsx
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
tsx
// src/App.tsx
function App() {
return <div>Hello World</div>
}
export default App
新的JSX转换方式
浏览器本身并不支持JSX,v17之前的旧版本JSX需要借助babel-plugin-transform-react-jsx
转化成React.createElement,因此即便没使用React也需要显示引入React
转换后的结果如下:
JS
import React from 'react';
function App() {
return React.createElement('h1', null, 'Hello world');
}
得益于v17版本引入的全新的JSX转换,即使无需引入React也能够使用JSX
jsx
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';
function App() {
return _jsx('h1', { children: 'Hello world' });
}
Typescript
但是继续开发时就会发现ts不支持JSX的语法,还需外要额外配置下ts

bash
# 安装下typescript和React的类型声明
pnpm i typescript @types/react @types/react-dom -D
# 生成tsconfig.json
tsc -init
设置下tsconfig.json
,找到"jsx": "preserve"
这一项,修改值为"jsx": "react-jsx"
, 此时TS的报错问题就解决了。
对根目录的index.html
做下修改,设置根结点,以模块形式引入main.tsx,就建立一个最基本的结构了
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
bash
pnpm vite
就可以启动本地服务器在浏览器上可以看到渲染出的内容了
代码格式
没有规范的代码格式会导致:
- 代码格式风格不统一,要花更多的时间看代码
- 没有统一的规范,会导致在多人协作的代码提交中会有很多的格式修改,造成不必要的消耗。非常影响排查问题
ESLint
ESLint 是一个根据方案识别并报告 ECMAScript/JavaScript 代码问题的工具,其目的是使代码风格更加一致并避免错误。
bash
# 初始化eslint配置
npx eslint --init
就直接使用现成的ESlint标准了,不折腾了
根目录新建一个.eslintignore
,里面存放Eslint不检测的文件
node_modules
.eslintrc.cjs
dist
Prettier
使用Prettier格式化代码,但是同时启用ESLint+Prettier,ESlint会先执行,Prettier后执行,导致代码格式反复横跳。
bash
pnpm i prettier eslint-plugin-prettier eslint-config-prettier -D
Eslint和prettier的冲突处理可以参考下prettier/eslint-plugin-prettier的配置方式
根目录下新建.prettierrc.cjs
,添加下Prettier配置
java
module.exports = {
printWidth: 100, //单行长度
tabWidth: 2, //缩进长度
useTabs: false, //使用空格代替tab缩进
semi: false, //句末使用分号
singleQuote: true, //使用单引号
quoteProps: 'as-needed', //仅在必需时为对象的key添加引号
jsxSingleQuote: true, // jsx中使用单引号
bracketSpacing: true, //在对象前后添加空格-eg: { foo: bar }
jsxBracketSameLine: true, //多属性html标签的'>'折行放置
arrowParens: 'always', //单参数箭头函数参数周围使用圆括号-eg: (x) => x
requirePragma: false, //无需顶部注释即可格式化
insertPragma: false, //在已被preitter格式化的文件顶部加上标注
endOfLine: 'auto', //结束行形式
embeddedLanguageFormatting: 'auto', //对引用代码进行格式化
}
.eslintrc.js
添加下规则,extends添加'plugin:prettier/recommended',关闭冲突规则。 plugins中添加'prettier',使得Eslint可以通过Prettier格式化代码
js
extends: ['standard-with-typescript','plugin:react/recommended','plugin:prettier/recommended'],
plugins: ['react', 'prettier'],
rules: {
'react/jsx-use-react': 0, // React V17开始JSX已经不再需要引入React
'react/react-in-jsx-scope': 0, // 同上
'import/first': 0, // 消除绝对路径必须要在相对路径前引入,
'no-mixed-spaces-and-tabs': 2, // 禁止空格和 tab 的混合缩进
'no-debugger': 2, // 禁止有debugger
'space-infix-ops': 2, // 要求操作符周围有空格
'space-before-blocks': 2, // 要求语句块之前有空格
'@typescript-eslint/explicit-function-return-type': 0, // 禁止函数必须要定义返回类型
},
Stylelint
使用Stylelint规范化CSS,可以控制下CSS属性的书写顺序,看起来更加工整。
项目使用的是less,需要安装对应的less相关的库
arduino
pnpm i less stylelint stylelint-config-standard-less postcss-less -D
可以根据项目和习惯自定义CSS属性的属性顺序,也可以直接使用社区方案
- 自定义CSS属性的属性顺序
bash
pnpm i stylelint-order -D
.stylelintrc.cjs
按照下面代码进行配置,order/properties-order中存放指定属性的前后顺序
js
module.exports = {
extends: ['stylelint-config-standard-less'],
overrides: [{ files: ['**/*.less'], customSyntax: 'postcss-less' }],
plugins: ['stylelint-order'],
rules: {
'order/order': ['custom-properties', 'declarations'],
'order/properties-order': ['width', 'height'],
},
}

- 使用社区方案 在awesome-stylelint上有其他已经配置好的方案,开盒即用
这里就以stylelint-config-recess-order
为例
css
pnpm i stylelint-config-recess-order -D
css
module.exports = {
extends: ['stylelint-config-standard-less', 'stylelint-config-recess-order'],
overrides: [{ files: ['**/*.less'], customSyntax: 'postcss-less' }],
plugins: [],
rules: {},
}
规范化提交
Commitizen
没有明确的提交规范会导致commit message非常随意,看着都头疼,非常影响定位问题和修bug的效率。
使用Commitizen规范下commit message。
bash
# 初始化下git
git init
# 安装配置Commitizen
pnpm install commitizen -D
commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact
# 后续就可以使用commitizen
git cz

husky
husky 是一个增强的 git hook 工具,可以在 git hook 的各个阶段执行我们在 package.json 中配置好的script。
bash
# 安装husky
pnpm dlx husky-init
pnpm install
代码检验
在commit之前,执行lint进行代码校验 在package.json
中添加下lint指令,使用eslint使用自动修复在src目录下ts和tsx文件
json
"scripts": {
"lint": "eslint --fix --ext .ts,.tsx src"
},
bash
npx husky add .husky/pre-commit "pnpm run lint"
提交信息检验
(可选) 在husky中添加prepare-commit-msg
bash
npx husky add .husky/prepare-commit-msg "exec < /dev/tty && npx cz --hook || true"
这样使用git commit会自动进入到commitizen,但是我不建议这样操作。 改变 git commit 命令原有的行为 ,失去快速提交的方式,像 git commit -m "chore: ..."
,可能其他项目参与者并不知道使用commitizen进行提交,即便输入符合规范的commit message信息,也会跳转到commitizen补全信息,而且这个命令行交互效果体验很差 但直接使用git commit提交,又不能限制提交的message,因此额外添加了
commitlint
进行判断
使用commitlint
工具对git commit提交的message信息进行检验
bash
pnpm i @commitlint/config-conventional @commitlint/cli -D
在package.json中添加
json
"scripts": {
"commitlint": "commitlint --config commitlint.config.cjs -e -V"
},
配置下husky的commit-msg
bash
npx husky add .husky/commit-msg 'pnpm run commitlint'
配置下commitlint的配置文件commitlint.config.cjs
cjs
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'perf', 'test', 'chore', 'revert', 'build'],
],
'type-case': [0],
'type-empty': [0],
'scope-empty': [0],
'scope-case': [0],
'subject-full-stop': [0, 'never'],
'subject-case': [0, 'never'],
'header-max-length': [0, 'always', 72],
},
}
现在既能够支持使用git cz方式提交commit,也能使用git commit按照规范快速提交
样式检验
sql
"scripts": {
"stylelint": "stylelint \"src/**/*.less\" --fix"
},
sql
npx husky add .husky/pre-commit 'pnpm run stylelint'
代码生成
Snippets
之前使用create-react-app搭建的项目时一直觉得那种只需要在输入几个像crf
的字符,就能快速生成一段代码的方式很方便,自己搭建的项目里面肯定也要加上这样的功能。
大部分的IDE都支持自定义代码片段功能,我就以VScode为例建立一个自定义的Snippets
VScode中ctrl+shift+P
搜索Snippets就能找到对应的设置。 可选择全局或者项目内设置snippets,设置后就会出现一个后缀名为code-snippets的文件。
Snippets有特定的语法,学习起来还要花时间,所以直接使用代码转Snippet的方案snippet-generator.app/,设置下代码和想使用的prefix前缀,cv下粘到code-snippets 文件里面就好了
hygen
在一些场景下像组件,都是有比较固定的文件结构和代码结构,直接copy旧组件还要删除多余的部分,这可太麻烦了,有时候还会带入一些bug。
既然是固定的结构那直接使用代码生成器根据指定模板生成即可。
使用hygen生成代码
bash
pnpm i hygen -D
hygen init self
初始化之后会创建一个根目录下创建一个_templates文件夹,内部存放模板,接下来我们就创建一个名为component的模板。
bash
hygen generator new component
模板的结构与ejs类似,前面一部分存放生成代码的位置,后面一部分就是ejs的写法,使用尖括号加百分号的标记来执行代码和插值。
ts
---
to: src/components/<%= name %>/index.tsx
---
import React,{ FC } from "react";
interface I<%= name %>Props {
}
const <%= name %>:FC<I<%= name %>Props> = (props) => {
return <div></div>
}
export default <%= name %>
根据这个模板可以在 src/components/指定的目录下根据模板生成指定的代码。
bash
hygen component new Button