前置条件
确保已安装:
- Node.js (v18+/v20+,推荐 v20)
- npm/pnpm/yarn(全程用 pnpm 演示,速度最快,npm/yarn 可直接替换命令)
- 代码编辑器(VS Code,推荐安装 ES7+ React 插件)
整体架构
-
工程架构对应:
- React + TS + Vite = Vue3 + TS + Vite(构建层完全一致)
- React Router v6 = Vue Router(路由配置、嵌套路由、跳转方式逻辑相通)
- Redux Toolkit = Pinia(切片 = Store 模块,内置 Immer 简化状态修改)
- Axios 封装 = Vue3 的 Axios 封装(拦截器、环境变量、接口封装逻辑完全一致)
-
核心步骤:
- 初始化 TS 项目 → 规范目录 → 集成路由 → 集成 Redux Toolkit → 封装 Axios → (可选)Redux 持久化。
-
TS 核心优化:
- 封装
useAppDispatch/useAppSelector简化 Redux 类型推导; - 接口返回值、组件 props、状态都定义明确的 TS 类型,和 Vue3 的 TS 用法一致。
- 封装
步骤 1:创建 React + TypeScript 项目(基于 Vite)
React 官方推荐 Vite 作为构建工具(和 Vue3 一致),先创建基础 TS 项目:
1.1 执行创建命令
打开终端,运行:
# 创建项目(指定 react-ts 模板)
pnpm create vite react-complete-demo -- --template react-ts
# 进入项目目录
cd react-complete-demo
# 安装基础依赖
pnpm install
1.2 验证基础项目运行
# 启动开发服务器
pnpm dev
浏览器访问 http://127.0.0.1:5173/,能看到 React + TS 初始页面即成功(类比 Vue3 + TS 项目启动)。
步骤 2:规范工程目录结构(类比 Vue3 工程)
先整理出企业级项目的目录结构(对应 Vue3 的 src/views、src/store、src/utils 等),提前创建空目录:
javascript
react-complete-demo/
├── src/
│ ├── api/ # 接口请求(对应 Vue3 的 src/api)
│ ├── assets/ # 静态资源(图片/样式,和 Vue3 一致)
│ ├── components/ # 公共组件(和 Vue3 一致)
│ ├── hooks/ # 自定义 Hooks(类比 Vue3 的 composables)
│ ├── pages/ # 页面组件(对应 Vue3 的 src/views)
│ ├── router/ # 路由配置(对应 Vue3 的 src/router)
│ ├── store/ # Redux 状态管理(对应 Vue3 的 src/store/Pinia)
│ ├── types/ # TS 类型定义(全局类型)
│ ├── utils/ # 工具函数(axios 封装、通用方法,和 Vue3 一致)
│ ├── App.tsx # 根组件(对应 Vue3 的 App.vue)
│ ├── main.tsx # 入口文件(对应 Vue3 的 main.ts)
│ └── vite-env.d.ts # TS 环境声明(自动生成)
├── .eslintrc.cjs # ESLint 配置(和 Vue3 一致)
├── index.html # 入口 HTML(和 Vue3 一致)
├── package.json # 项目配置
└── vite.config.ts # Vite 配置
2.1 创建目录(终端执行)
# 在 src 下创建目录
mkdir -p src/api src/hooks src/pages src/router src/store src/types src/utils
步骤 3:集成 React Router 路由(对应 Vue Router)
React Router 是 React 官方路由库(类比 Vue Router),版本选最新的 v6+。
3.1 安装 React Router
pnpm install react-router-dom
3.2 配置路由(核心步骤)
3.2.1 创建页面组件(src/pages)
先创建 2 个测试页面(首页、关于页),对应 Vue3 的 views 下的页面:
src/pages/Home/Home.tsx(首页):
javascript
import { FC } from 'react';
// FC 是 React 函数组件的 TS 类型(Functional Component)
const Home: FC = () => {
return (
<div className="home-page">
<h1>首页</h1>
<p>React Router 路由测试 - 首页</p>
</div>
);
};
export default Home;
src/pages/About/About.tsx(关于页):
javascript
import { FC } from 'react';
const About: FC = () => {
return (
<div className="about-page">
<h1>关于页</h1>
<p>React Router 路由测试 - 关于页</p>
</div>
);
};
export default About;
3.2.2 创建路由配置文件(src/router/index.tsx)
对应 Vue3 的 src/router/index.ts:
javascript
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Home from '../pages/Home/Home';
import About from '../pages/About/About';
import App from '../App';
// 创建路由实例(对应 Vue3 的 createRouter)
const router = createBrowserRouter([
{
path: '/', // 根路径
element: <App />, // 根组件(对应 Vue3 的 layout)
children: [ // 嵌套路由(对应 Vue3 的 children)
{ path: '', element: <Home /> }, // 默认子路由
{ path: 'about', element: <About /> }, // 关于页路由
],
},
]);
// 封装路由提供者组件(供入口文件使用)
const AppRouter: FC = () => {
return <RouterProvider router={router} />; // 对应 Vue3 的 <RouterView />
};
export default AppRouter;
3.2.3 修改根组件(src/App.tsx)
添加路由出口(对应 Vue3 的 <RouterView />):
javascript
import { FC } from 'react';
import { Outlet, Link } from 'react-router-dom'; // Outlet=路由出口,Link=路由跳转(对应 Vue3 的 <RouterLink>)
import './App.css';
const App: FC = () => {
return (
<div className="app-container">
{/* 导航栏(路由跳转) */}
<nav>
<Link to="/" className="nav-link">首页</Link>
<Link to="/about" className="nav-link">关于</Link>
</nav>
{/* 路由出口:渲染匹配的子路由组件 */}
<Outlet />
</div>
);
};
export default App;
3.2.4 修改入口文件(src/main.tsx)
挂载路由(替代默认的 App 组件):
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import AppRouter from './router'; // 导入路由组件
import './index.css';
// 挂载到 DOM(对应 Vue3 的 createApp().mount('#app'))
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<AppRouter /> {/* 挂载路由 */}
</React.StrictMode>,
);
3.2.5 添加简单样式(src/App.css)
css
.app-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.nav-link {
margin-right: 20px;
text-decoration: none;
color: #42b983;
}
.nav-link:hover {
color: #359469;
}
.home-page, .about-page {
margin-top: 20px;
}
3.3 验证路由功能
重启项目 pnpm dev,访问 http://127.0.0.1:5173/,点击「首页 / 关于」能切换页面即成功(类比 Vue Router 的路由跳转)。
步骤 4:集成 Redux Toolkit 状态管理(对应 Pinia)
Redux 是 React 生态最主流的状态管理库,Redux Toolkit(RTK) 是官方推荐的简化版(类比 Pinia 简化 Vuex),比原生 Redux 简单 N 倍。
4.1 安装 Redux 依赖
# RTK 核心 + React 绑定
pnpm install @reduxjs/toolkit react-redux
4.2 配置 Redux(核心步骤)
4.2.1 创建 Redux Store(src/store/index.ts)
对应 Vue3 的 src/store/index.ts(创建 Pinia 实例):
javascript
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './features/counterSlice'; // 后续创建的计数器切片
// 配置 Store(对应 Pinia 的 createPinia())
export const store = configureStore({
reducer: {
// 注册切片(对应 Pinia 的 defineStore)
counter: counterReducer,
},
});
// 导出 TS 类型(方便组件中使用)
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
4.2.2 创建 Redux 切片(src/store/features/counterSlice.ts)
切片 = Pinia 的 Store 模块,包含状态、修改方法(无需手写 action):
javascript
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// 定义状态类型
interface CounterState {
count: number;
name: string;
}
// 初始状态(对应 Pinia 的 state 初始值)
const initialState: CounterState = {
count: 0,
name: 'React Redux 计数器',
};
// 创建切片(对应 Pinia 的 defineStore)
const counterSlice = createSlice({
name: 'counter', // 切片名(唯一)
initialState,
// 同步修改方法(对应 Pinia 的 actions)
reducers: {
increment: (state) => {
state.count += 1; // RTK 内置 Immer,可直接修改状态(无需返回新对象)
},
decrement: (state) => {
state.count -= 1;
},
// 带参数的修改方法
incrementByAmount: (state, action: PayloadAction<number>) => {
state.count += action.payload;
},
},
});
// 导出修改方法(供组件调用)
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
// 导出切片 reducer(供 store 注册)
export default counterSlice.reducer;
4.2.3 创建自定义 Hooks(简化 TS 类型)
src/hooks/useAppDispatch.ts:
javascript
import { useDispatch } from 'react-redux';
import type { AppDispatch } from '../store';
// 封装 dispatch Hook(自动推导类型)
export const useAppDispatch = () => useDispatch<AppDispatch>();
src/hooks/useAppSelector.ts:
javascript
import { useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState } from '../store';
// 封装 selector Hook(自动推导状态类型)
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
4.2.4 在组件中使用 Redux(修改首页组件)
src/pages/Home/Home.tsx:
javascript
import { FC } from 'react';
// 导入自定义 Hooks 和 Redux 方法
import { useAppSelector, useAppDispatch } from '../../hooks/useAppDispatch';
import { increment, decrement, incrementByAmount } from '../../store/features/counterSlice';
const Home: FC = () => {
// 获取 Redux 状态(对应 Pinia 的 store.count)
const { count, name } = useAppSelector((state) => state.counter);
// 获取 dispatch(用于调用修改方法)
const dispatch = useAppDispatch();
return (
<div className="home-page">
<h1>{name}</h1>
<p>当前计数:{count}</p>
{/* 调用 Redux 修改方法(对应 Pinia 的 store.increment()) */}
<button onClick={() => dispatch(increment())}>+1</button>
<button onClick={() => dispatch(decrement())} style={{ marginLeft: 10 }}>-1</button>
<button onClick={() => dispatch(incrementByAmount(5))} style={{ marginLeft: 10 }}>+5</button>
</div>
);
};
export default Home;
4.2.5 挂载 Redux 到项目(修改入口文件)
src/main.tsx:
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux'; // Redux 提供者(对应 Pinia 的 <PiniaProvider>)
import { store } from './store'; // 导入 Redux Store
import AppRouter from './router';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
{/* 包裹路由,让所有组件能访问 Redux */}
<Provider store={store}>
<AppRouter />
</Provider>
</React.StrictMode>,
);
4.3 验证 Redux 功能
重启项目,首页计数器点击按钮能正常增减,状态会被 Redux 缓存(刷新后重置,后续可加持久化)。
步骤 5:集成 Axios 并封装请求(对应 Vue3 的 Axios 封装)
Axios 在 React 中用法和 Vue3 几乎一致,重点是封装请求拦截、响应拦截、统一错误处理。
5.1 安装 Axios
pnpm install axios
5.2 封装 Axios(src/utils/request.ts)
javascript
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';
// 创建 Axios 实例(对应 Vue3 的 axios.create)
const service: AxiosInstance = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL, // 基础地址(从环境变量读取)
timeout: 5000, // 超时时间
headers: {
'Content-Type': 'application/json',
},
});
// 请求拦截器(添加 token 等)
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
// 示例:添加 token 到请求头(从 localStorage 读取)
const token = localStorage.getItem('token');
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error: AxiosError) => {
// 请求错误处理
console.error('请求错误:', error);
return Promise.reject(error);
},
);
// 响应拦截器(统一处理响应/错误)
service.interceptors.response.use(
(response: AxiosResponse) => {
// 简化返回数据(只返回 data)
return response.data;
},
(error: AxiosError) => {
// 统一错误处理
console.error('响应错误:', error);
const errMsg = error.response?.data?.message || error.message || '请求失败';
// 示例:弹框提示错误(可集成 UI 库如 Antd)
alert(errMsg);
// 身份验证失败(401):跳转到登录页
if (error.response?.status === 401) {
localStorage.removeItem('token');
window.location.href = '/login';
}
return Promise.reject(error);
},
);
// 导出封装后的 Axios 实例
export default service;
5.3 创建环境变量(根目录:.env.development)
对应 Vue3 的 .env 文件,配置接口基础地址:
# 开发环境接口地址
VITE_API_BASE_URL = 'https://jsonplaceholder.typicode.com'
5.4 封装接口请求(src/api/post.ts)
以 JSONPlaceholder 测试接口为例(模拟获取文章列表):
javascript
import request from '../utils/request';
// 定义文章类型
export interface Post {
id: number;
title: string;
body: string;
userId: number;
}
// 获取文章列表
export const getPostList = (params?: { userId?: number }) => {
return request.get<Post[]>('/posts', { params });
};
// 获取单篇文章
export const getPostDetail = (id: number) => {
return request.get<Post>(`/posts/${id}`);
};
5.5 在组件中使用接口请求(修改 About 页面)
src/pages/About/About.tsx:
javascript
import { FC, useState, useEffect } from 'react';
import { getPostList, Post } from '../../api/post';
const About: FC = () => {
// 定义状态存储文章列表(对应 Vue3 的 ref<Post[]>)
const [postList, setPostList] = useState<Post[]>([]);
const [loading, setLoading] = useState<boolean>(true);
// 加载数据(对应 Vue3 的 onMounted)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
// 调用接口
const data = await getPostList({ userId: 1 });
setPostList(data);
} catch (error) {
console.error('获取文章失败:', error);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
return (
<div className="about-page">
<h1>关于页 - 接口请求示例</h1>
{loading ? (
<p>加载中...</p>
) : (
<ul>
{postList.map((post) => (
<li key={post.id} style={{ margin: 10 <|card|>: 0 }}>
<h3>{post.title}</h3>
<p>{post.body.slice(0, 100)}...</p>
</li>
))}
</ul>
)}
</div>
);
};
export default About;
5.6 验证接口请求
重启项目,点击「关于页」能看到加载出的文章列表(从 JSONPlaceholder 获取),即 Axios 封装成功。
步骤 6:添加 Redux 持久化(可选,对应 Pinia 持久化)
让 Redux 状态刷新后不丢失(类比 Pinia 的 pinia-plugin-persistedstate):
6.1 安装依赖
pnpm install redux-persist
6.2 修改 Redux Store 配置(src/store/index.ts)
javascript
import { configureStore } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // 使用 localStorage 存储
import counterReducer from './features/counterSlice';
// 持久化配置
const counterPersistConfig = {
key: 'counter', // 存储的 key
storage, // 存储方式(localStorage)
// whitelist: ['count'], // 只持久化 count 字段(可选)
};
// 包装 reducer
const persistedCounterReducer = persistReducer(counterPersistConfig, counterReducer);
export const store = configureStore({
reducer: {
counter: persistedCounterReducer, // 使用持久化后的 reducer
},
// 禁用序列化检查(解决 persist 警告)
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
// 创建持久化 store
export const persistor = persistStore(store);
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
6.3 修改入口文件(src/main.tsx)
javascript
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react'; // 持久化网关
import { store, persistor } from './store';
import AppRouter from './router';
import './index.css';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}>
{/* 持久化网关(loading 可自定义加载组件) */}
<PersistGate loading={null} persistor={persistor}>
<AppRouter />
</PersistGate>
</Provider>
</React.StrictMode>,
);
验证:修改计数器数值后刷新页面,数值不会重置。
步骤7:添加.gitignore文件
要避免上传不必要的文件到 GitHub,核心是通过 .gitignore 文件配置忽略规则,我会帮你梳理 React 项目需要忽略的文件,以及完整的上传步骤。
一、核心:配置 .gitignore 文件
.gitignore 是 Git 的忽略配置文件,会告诉 Git 哪些文件 / 目录不需要追踪(不上传)。React + TypeScript + Vite 项目有明确的忽略规范,我会给你一份「开箱即用」的配置。
1.1 创建 / 完善 .gitignore 文件
在项目根目录下创建 .gitignore 文件(如果已有则直接修改),粘贴以下内容:
# 依赖目录(核心!不上传 node_modules,体积大且可通过 npm install 重建)
node_modules/
# 构建产物(打包后的 dist 目录,上线才需要,无需上传)
dist/
build/
# 环境变量文件(包含敏感配置如接口地址、密钥,绝对不上传)
.env
.env.local
.env.development.local
.env.production.local
.env.test.local
# npm 日志文件
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# 编辑器/IDE 配置(个人开发工具配置,无需共享)
.idea/
.vscode/
*.swp
*.swo
.DS_Store # Mac 系统文件
Thumbs.db # Windows 系统文件
# 缓存文件
*.cache
.eslintcache
# TypeScript 编译产物(如果手动编译会生成,Vite 项目一般不需要)
*.tsbuildinfo
lib/
out/
# Redux 持久化临时文件(可选)
redux-persist/
# 临时文件
tmp/
temp/
1.2 关键忽略项说明(针对你的项目)
| 忽略项 | 原因 |
|---|---|
| node_modules/ | 依赖包体积大(几百 MB),其他人克隆项目后执行 pnpm install 即可重建 |
| dist/ | 打包后的产物,属于编译结果,应通过源码构建而非直接上传 |
| .env* | 包含敏感配置(如接口基础地址、密钥),避免泄露;可传 .env.example 示例 |
| .vscode/ | 个人 VS Code 配置(如插件、格式化规则),团队协作无需共享 |
| .DS_Store/Thumbs.db | 系统自动生成的垃圾文件,无实际意义 |
二、可选:添加 .env.example 示例文件
如果需要告诉协作成员环境变量的格式(但不上传真实值),可以在根目录创建 .env.example 文件(这个文件需要上传):
# 环境变量示例(请复制为 .env.development 并修改真实值)
VITE_API_BASE_URL = 'https://your-api-base-url.com'
# VITE_TOKEN = 'your-token-here'(示例敏感配置)
三、完整的 GitHub 上传步骤
3.1 前置条件
- 已注册 GitHub 账号,本地安装 Git(执行
git --version检查); - 项目根目录已配置好
.gitignore; - 清空不必要的文件(如 node_modules、dist 已在 .gitignore 中,无需手动删除)。
3.2 上传步骤(终端执行)
步骤 1:初始化 Git 仓库(首次上传)
# 进入项目根目录
cd react-complete-demo
# 初始化 Git 仓库
git init
步骤 2:添加文件到暂存区
# 添加所有未被忽略的文件(. 代表所有)
git add .
# 验证:查看已添加的文件(可选)
git status
执行 git status 后,你会看到 node_modules、dist、.env 等文件显示为「Untracked」(未追踪),这说明忽略规则生效了。
步骤 3:提交暂存区文件
# 提交并添加说明(必填)
git commit -m "初始化 React 项目:TS + 路由 + Redux + Axios"
步骤 4:关联 GitHub 远程仓库
-
先在 GitHub 上创建一个空仓库(不要勾选「Add a README file」「Add .gitignore」);
-
复制仓库的 HTTPS/SSH 地址(如
https://github.com/你的用户名/react-complete-demo.git); -
执行关联命令:
关联远程仓库(origin 是仓库别名)
git remote add origin https://github.com/你的用户名/react-complete-demo.git
验证关联(可选)
git remote -v
步骤 5:推送到 GitHub
# 推送到 main 分支(默认分支)
git push -u origin main
- 如果是第一次推送,可能需要登录 GitHub 验证身份;
- 用 SSH 地址推送需配置 SSH 密钥(推荐,避免每次输密码)。
四、常见问题解决
问题 1:已提交的文件(如 node_modules)想忽略但删不掉
如果不小心已经提交了 node_modules,先删除本地追踪再推送到远程:
# 从 Git 追踪中移除 node_modules(保留本地文件)
git rm -r --cached node_modules
# 重新提交
git commit -m "移除已提交的 node_modules"
# 推送到远程
git push origin main
问题 2:.gitignore 配置不生效
原因:.gitignore 只对未被追踪的文件生效,解决方法:
# 清空 Git 缓存
git rm -r --cached .
# 重新添加所有文件
git add .
# 重新提交
git commit -m "修复 .gitignore 不生效问题"
# 推送
git push origin main
问题 3:推送时提示「fatal: remote origin already exists」
说明已关联过远程仓库,先删除旧关联再重新关联:
# 删除旧的 origin 关联
git remote rm origin
# 重新关联新仓库
git remote add origin 你的新仓库地址