前言:React项目一般都是基于create-react-app等脚手架快速搭建,初始化项目。现在有点空闲的时间,琢磨如何从0到1进行搭建。
搭建基础项目
一、初始化&基本配置
bash
npm init && git init
修改package.json
json
{
"name": "react-ts-web",
"version": "1.0.0",
"description": "a react typescript web template",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"react",
"typescript",
"vite"
],
"homepage": "https://github.com/xxx/xxx",
"bugs": {
"url": "https://github.com/xxx/xxx/issues"
},
"author": "xxx <xxx@xx.com>",
"license": "ISC"
}
package.json
的全部字段描述 参考
二、安装核心依赖,搭建开发环境
1. 安装依赖
bash
pnpm add react react-dom && pnpm add -D vite typescript @types/react-dom @types/react @types/node
2. tsconfig.json
初始化,修改
bash
npx tsc --init
json
{
"references": [{ "path": "./tsconfig.vite.json" }], // 指定工程引用依赖
"compilerOptions": {
"target": "ES2020", // 指定编译的ECMAScript目标版本
"lib": ["ES2020", "DOM", "DOM.Iterable"], // 编译过程中需要引入的库文件的列表
"jsx": "react-jsx", // 指定 jsx 代码的生成,无需在每个jsx文件中引入React
"module": "ESNext", // 指定生成哪个模块系统代码
"moduleResolution": "Node", // 决定如何处理模块
"allowImportingTsExtensions": true, // 允许TypeScript文件通过TypeScript特定的扩展名如.ts, .mts, 或 .tsx互相导入
"resolveJsonModule": true, // 允许引入 JSON 文件
"allowJs": true, // 是否允许编译javascript文件
"noEmit": true, // 设置是否输出 js 文件,一般是设置为 false,将打包等工作交给 vite/webpack 等工具
"isolatedModules": true, // 将每个文件做为单独的模块
"esModuleInterop": true, // 支持合成模块的默认导入
"forceConsistentCasingInFileNames": true, // forceConsistentCasingInFileNames
"strict": true, // 启用所用严格的类型检查
"skipLibCheck": true, // 跳过对 .d.ts 文件的类型检查
"types": ["node", "vite/client"],
"baseUrl": ".", // 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
"paths": {
// 用于设置模块名到基于baseUrl的路径映射
"@/*": ["./src/*"]
}
},
"include": ["src", "@types"],
"exclude": ["node_modules"]
}
3. 新增tsconfig.vite.json
json
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}
作用:
tsconfig.vite.json
专门为vite.config.ts
提供的ts配置文件。
单独配置原因: 运行环境不同,项目的代码运行在浏览器环境,而vite.config.ts
运行在Node环境。相应的两者需要的接口类型也各不一样。
4. 按照所示,搭建基础架子

App.tsx
tsx
export default function App() {
return (
<div>
<h1>Welcome to React Web.</h1>
</div>
);
}
main.tsx
tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
const root = createRoot(document.querySelector('#app')!);
root.render(
<StrictMode>
<App />
</StrictMode>,
);
使用
StrictMode
来启用组件树内部的额外开发行为和警告
vite.config.ts
ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
});
修改 package.json
, 添加 vite
启动、打包脚本和 ts 类型检测,内容太多就只展示新增内容
json
{
...
"scripts": {
"serve": "vite",
"build": "vite build",
"tsc": "tsc --noEmit"
},
}
新增 .gitignore
。git提交时,忽略文件提交
sh
node_modules
dist
.DS_Store
三、配置ESLint + prettier
eslint: 代码检查工具,主要用来发现代码错误、统一代码风格。 prettier: 对代码进行格式化,并不关注代码质量潜在问题的检查,倾向于团队的代码风格的规范或统一。
1. ESLint 配置
这里使用alloy团队的一套 规范,基本上按照文档配置操作即可。
这里的 eslint 配置个人比较喜欢用json,就采用了json格式
2. 将 prettier 作为 ESLint 的规则来使用,让 ESLint 托管 prettier。
bash
pnpm add -D eslint-config-prettier prettier eslint-plugin-prettier eslint-config-prettier
修改 .eslintrc.json
json
{
"root": true,
"parserOptions": {
"project": ["./tsconfig.json", "./tsconfig.vite.json"]
},
"settings": {
"react": {
"version": "detect"
}
},
"extends": ["alloy", "alloy/react", "alloy/typescript", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": true
},
"globals": {},
"rules": {
"prettier/prettier": "error"
},
"ignorePatterns": ["dist", "node_modules"]
}
新增 .prettierrc
sh
"eslint-config-alloy/.prettierrc.js"
3. vite.config.ts
配置ESLint
typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
+ import eslint from 'vite-plugin-eslint'
export default defineConfig({
- plugins: [react()]
+ plugins: [react(), eslint()],
});
四、配置husky + lint-staged
husky: 给git添加hook钩子,在特定的 Git 操作触发之前或之后执行预定义的脚本
lint-staged: 在 Git 暂存区中运行指定脚本的工具。它通常与 Husky 一起使用
1. 安装husky、lint-staged依赖
bash
pnpm dlx husky-init && pnpm install
pnpm add -D lint-staged
2. 新增 .lintstagedrc.json
json
{
"*.{js,jsx}": ["eslint --fix"],
"*.{ts,tsx}": ["eslint --fix", "bash -c tsc"]
}
通过
bash -c tsc
,让tsc识别项目中的tsconfig.json
配置
五、git commit 规范提交
1. 全局安装 commitizen
bash
pnpm add commitizen -g
2. 项目根目录安装
bash
pnpm add -D cz-git
3. package.json
新增内容
json
{
...
"scripts": {
"commit": "git-cz"
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
},
}
4. 添加commitlint.config.cjs
js//
/** @type {import('cz-git').UserConfig} */
module.exports = {
rules: {
// @see: https://commitlint.js.org/#/reference-rules
},
prompt: {
alias: { fd: 'docs: fix typos' },
messages: {
type: '选择你要提交的类型 :',
scope: '选择一个提交范围(可选):',
customScope: '请输入自定义的提交范围 :',
subject: '填写简短精炼的变更描述 :\n',
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
footerPrefixesSelect: '选择关联issue前缀(可选):',
customFooterPrefix: '输入自定义issue前缀 :',
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
confirmCommit: '是否提交或修改commit ?'
},
types: [
{ value: 'feat', name: 'feat: 新增功能 | A new feature' },
{ value: 'fix', name: 'fix: 修复缺陷 | A bug fix' },
{ value: 'docs', name: 'docs: 文档更新 | Documentation only changes' },
{ value: 'style', name: 'style: 代码格式 | Changes that do not affect the meaning of the code' },
{ value: 'refactor', name: 'refactor: 代码重构 | A code change that neither fixes a bug nor adds a feature' },
{ value: 'perf', name: 'perf: 性能提升 | A code change that improves performance' },
{ value: 'test', name: 'test: 测试相关 | Adding missing tests or correcting existing tests' },
{ value: 'build', name: 'build: 构建相关 | Changes that affect the build system or external dependencies' },
{ value: 'ci', name: 'ci: 持续集成 | Changes to our CI configuration files and scripts' },
{ value: 'revert', name: 'revert: 回退代码 | Revert to a commit' },
{ value: 'chore', name: 'chore: 其他修改 | Other changes that do not modify src or test files' },
],
useEmoji: false,
emojiAlign: 'center',
themeColorCode: '',
scopes: [],
allowCustomScopes: true,
allowEmptyScopes: true,
customScopesAlign: 'bottom',
customScopesAlias: 'custom',
emptyScopesAlias: 'empty',
upperCaseSubject: false,
markBreakingChangeMode: false,
allowBreakingChanges: ['feat', 'fix'],
breaklineNumber: 100,
breaklineChar: '|',
skipQuestions: [],
issuePrefixes: [
// 如果使用 gitee 作为开发管理
{ value: 'link', name: 'link: 链接 ISSUES 进行中' },
{ value: 'closed', name: 'closed: 标记 ISSUES 已完成' }
],
customIssuePrefixAlign: 'top',
emptyIssuePrefixAlias: 'skip',
customIssuePrefixAlias: 'custom',
allowCustomIssuePrefix: true,
allowEmptyIssuePrefix: true,
confirmColorize: true,
maxHeaderLength: Infinity,
maxSubjectLength: Infinity,
minSubjectLength: 0,
scopeOverrides: undefined,
defaultBody: '',
defaultIssues: '',
defaultScope: '',
defaultSubject: ''
}
}
六、添加changelog
bash
pnpm add standard-version
1. package.json
添加脚本
json
{
...
"release": "standard-version"
}
2. 首次生成changelog
version:1.0.0版本
bash
npx standard-version --first-release
功能
一、SASS支持
bash
pnpm add -D sass
1. 新增文件 src/App.scss
scss
.main {
h1 {
color: red;
}
}
2. src/App.tsx
修改
jsx
import './App.scss';
export default function App() {
return (
<div className="main">
<h1>Welcome to React Web.</h1>
</div>
);
}
二、图片支持
1. 新增 ./src/assets/react.png
2. src/App.tsx
修改
jsx
import './App.scss';
import ReactLogo from './assets/React.png';
export default function App() {
return (
<div className="main">
<h1>Welcome to React Web.</h1>
<img src={ReactLogo} alt="logo" />
</div>
);
}
3. 新增@types/assets.d.ts
ts
declare module '*.png';
declare module '*.svg';
declare module '*.jpeg';
declare module '*.jpg';
三、设置路径别名
1. vite.config.ts
修改
ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import eslint from 'vite-plugin-eslint';
+ import path from 'path';
export default defineConfig({
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, 'src'),
+ },
+ extensions: ['.js', '.jsx', '.ts', '.tsx'],
+ },
plugins: [react(), eslint()],
});
2. eslint 修改
bash
pnpm add eslint-import-resolver-alias
.eslintrc.json
json
{
"root": true,
"parserOptions": {
"project": ["./tsconfig.json", "./tsconfig.vite.json"]
},
"settings": {
"react": {
"version": "detect"
},
+ "import/resolver": {
+ "alias": [["@", "./src"]]
+ }
},
"extends": ["alloy", "alloy/react", "alloy/typescript", "prettier"],
"plugins": ["prettier"],
"env": {
"browser": true
},
"globals": {},
"rules": {
"prettier/prettier": "error",
"no-return-assign": "off"
},
"ignorePatterns": ["dist", "node_modules"]
}
3. tsconfig.json
修改
json
{
"references": [{ "path": "./tsconfig.vite.json" }], // 指定工程引用依赖
"compilerOptions": {
"target": "ES2020", // 指定编译的ECMAScript目标版本
"lib": ["ES2020", "DOM", "DOM.Iterable"], // 编译过程中需要引入的库文件的列表
"jsx": "react-jsx", // 指定 jsx 代码的生成,无需在每个jsx文件中引入React
"module": "ESNext", // 指定生成哪个模块系统代码
"moduleResolution": "Node", // 决定如何处理模块
"allowImportingTsExtensions": true, // 允许TypeScript文件通过TypeScript特定的扩展名如.ts, .mts, 或 .tsx互相导入
"resolveJsonModule": true, // 允许引入 JSON 文件
"allowJs": true, // 是否允许编译javascript文件
"noEmit": true, // 设置是否输出 js 文件,一般是设置为 false,将打包等工作交给 vite/webpack 等工具
"isolatedModules": true, // 将每个文件做为单独的模块
"esModuleInterop": true, // 支持合成模块的默认导入
"forceConsistentCasingInFileNames": true, // forceConsistentCasingInFileNames
"strict": true, // 启用所用严格的类型检查
"skipLibCheck": true, // 跳过对 .d.ts 文件的类型检查
+ "baseUrl": ".", // 用于设置解析非相对模块名称的基本目录,相对模块不会受到baseUrl的影响
+ "paths": { // 用于设置模块名到基于baseUrl的路径映射
+ "@/*": ["./src/*"]
+ }
},
"include": ["src"],
"exclude": ["node_modules"]
}
四、React路由配置
1. 安装依赖
bash
pnpm add react-router-dom
2. 新增页面
src/pages/404.tsx
tsx
import { useNavigate } from 'react-router-dom';
export default () => {
const navigate = useNavigate();
const goBack = () => {
navigate(-1);
};
return (
<>
<h1>About Page</h1>
<button onClick={goBack}>返回</button>
</>
);
};
src/pages/Home.tsx
tsx
import { Link } from 'react-router-dom';
export default () => {
return (
<>
<h1>Home Page</h1>
<Link to="/about">页面跳转</Link>
</>
);
};
src/pages/About.tsx
tsx
import { useNavigate } from 'react-router-dom';
export default () => {
const navigate = useNavigate();
const goBack = () => {
navigate(-1);
};
return (
<>
<h1>About Page</h1>
<button onClick={goBack}>返回</button>
</>
);
};
3. 移除src/App.tsx
4. 定义路由表src/router/index.ts
typescript
import About from '../pages/About';
import Home from '../pages/Home';
import NoFound from '../pages/404';
import { RouteObject } from 'react-router-dom';
const routeConfig: RouteObject[] = [
{
path: '/',
Component: Home,
},
{
path: '/about',
Component: About,
},
{
path: '*',
Component: NoFound,
},
];
export default routeConfig;
5. 入口文件main.tsx
调整
tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import routeConfig from './router';
const router = createBrowserRouter(routeConfig);
const root = createRoot(document.querySelector('#app')!);
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
);
五、状态管理机制
目前 React 的状态管理机制解决方案是百花齐放,如:
redux
、react-toolkit
、zustand
、recoil
等。为了降低上手成本,这里使用目前比较火的zustand
。具体就不展开对比各种状态管理机制的不同了。
1. 安装依赖
bash
pnpm add zustand
2. 新增 src/store/counter.ts
ts
import { create } from 'zustand';
interface CounterStore {
num: number;
increase: () => void;
}
const useCounterStore = create<CounterStore>()((set) => ({
num: 0,
increase: () => set((state) => ({ num: (state.num += 1) })),
}));
export default useCounterStore;
3. 新增组件 src/components/CountNum.tsx
tsx
import useCounterStore from '@/store/counter';
export default () => {
const num = useCounterStore((state) => state.num);
return <p>{num} </p>;
};
4. 修改 src/pages/Home.tsx
tsx
import { Link } from 'react-router-dom';
+ import useCounterStore from '@/store/counter';
+ import CountNum from '@/components/CountNum';
export default () => {
+ const increase = useCounterStore((state) => state.increase);
return (
<>
<h1>Home Page</h1>
+ <CountNum />
+ <button onClick={increase}>加一</button>
+ <br />
<Link to="/about">页面跳转</Link>
</>
);
};
六、 多环境打包
可以为每个不同的环境,设置特定的内容:接口请求地址、OSS配置等
1. 新增配置文件
env/.env.development
sh
VITE_PROJECT_ENV=development
VITE_APP_TITLE=My App (development)
env/.env.production
sh
VITE_PROJECT_ENV=production
VITE_APP_TITLE=My App (production)
env/.env.staging
sh
VITE_PROJECT_ENV=staging
VITE_APP_TITLE=My App (staging)
env/.env.testing
sh
VITE_PROJECT_ENV=testing
VITE_APP_TITLE=My App (testing)
2. vite.config.ts
调整
ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import eslint from 'vite-plugin-eslint';
import path from 'path';
export default defineConfig({
+ envDir: './env',
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
plugins: [react(), eslint()],
});
3. package.json
添加脚本命令
json
...
"scripts": {
...
"start:dev": "vite --mode development",
"start:test": "vite --mode testing",
"start:staging": "vite --mode staging",
"start:prod": "vite --mode production",
"build": "pnpm build:prod",
"build:dev": "pnpm tsc && vite build --mode development",
"build:test": "pnpm tsc && vite build --mode testing",
"build:staging": "pnpm tsc && vite build --mode staging",
"build:prod": "pnpm tsc && vite build --mode production",
}
4. 页面模版修改
tsx
import { Link } from 'react-router-dom';
import useCounterStore from '@/store/counter';
import CountNum from '@/components/CountNum';
+ import { useState } from 'react';
export default () => {
const increase = useCounterStore((state) => state.increase);
+ const title = useState(import.meta.env.VITE_APP_TITLE);
return (
<>
- <h1>Home Page</h1>
+ <p>{title}</p>
<CountNum />
<button onClick={increase}>加一</button>
<br />
<Link to="/about">页面跳转</Link>
</>
);
};
后续内容,待补充。。。 欢迎指出问题,继续完善!👏