项目安装- React + TypeScript

前置条件

确保已安装:

  • 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 前置条件

  1. 已注册 GitHub 账号,本地安装 Git(执行 git --version 检查);
  2. 项目根目录已配置好 .gitignore
  3. 清空不必要的文件(如 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 远程仓库
  1. 先在 GitHub 上创建一个空仓库(不要勾选「Add a README file」「Add .gitignore」);

  2. 复制仓库的 HTTPS/SSH 地址(如 https://github.com/你的用户名/react-complete-demo.git);

  3. 执行关联命令:

    关联远程仓库(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 你的新仓库地址
相关推荐
光辉GuangHui1 小时前
SDD 实践:OpenSpec + Superpowers 整合创建自定义工作流
前端·后端
ssshooter1 小时前
infer,TS 类型系统的手术刀
前端·面试·typescript
用户3167361303421 小时前
图片懒加载,我总结了三个方式
前端
灰太狼大大王1 小时前
2026 前端基石:HTML5 全景知识体系指南(从入门到架构师思维)
前端
米丘1 小时前
vue-router 5.x 文件式路由
前端·vue.js
始持1 小时前
第十五讲 本地存储
前端·flutter
AAA不会前端开发2 小时前
从零手写 React:深度解析 Fiber 架构与 Hooks 实现
react.js·前端框架
敲代码的约德尔人2 小时前
React Hooks 最佳实践指南
react.js
不甜情歌2 小时前
JS 拷贝:浅拷贝 / 深拷贝原理 + 常用方法
前端·javascript