从 Vue 到 React:Vite 构建 React 项目的核心指南

69 阅读8分钟

对于刚完成 Vue 项目框架学习的前端新人而言,从 Vue 的选项式 API、响应式系统和组件模型转向 React 时,往往会面临语法体系、思维模式的切换挑战。本文基于一个使用 Vite 构建的 React 项目,结合 Vue 开发者的认知习惯,全面介绍 React 应用的基本架构、核心概念和最佳实践,帮助读者快速搭建 React 项目的知识框架,实现从 Vue 到 React 的平滑过渡。

🚀 一、 项目初始化

使用 Vite 创建 React 项目

在现代前端开发中,我们使用 Vite 作为构建工具来快速启动 React 项目:

npm init vite

为什么选择 Vite?

  1. 极致的冷启动速度:Vite 采用 ESM(ES Modules)模块化方案,无需打包即可启动开发服务器
  2. 现代化的工具链:原生支持 TypeScript、JSX、CSS 预处理器等
  3. 快速的热更新:基于浏览器原生 ESM,HMR 速度飞快

项目生命周期

一个完整的项目开发流程通常包括以下循环阶段:

image.png

  • dev:开发阶段,使用 

    npm run dev
    

     启动开发服务器

image.png

  • test:测试阶段,进行单元测试和集成测试

  • production:生产环境,打包部署到线上

我们可以发现,在 App.jsx 里面打印 count ,如图:

image.png

会有两个数字出现

这是因为在 main.jsx中,使用 StrictMode

image.png

核心原因是 StrictMode 会故意让某些函数 / 方法执行两次,这是 React 的设计行为,目的是帮你发现代码中的潜在问题,而且这个行为只在开发环境(development)中存在,生产环境(production)不会有

哪些情况会被执行两次?

在 StrictMode 下,React 会对以下操作进行重复调用(通常是两次):

  1. 函数组件的主体代码(比如组件内部的 console.log(count));
  2. useStateuseMemouseCallback 等 Hook 的初始化函数
  3. 类组件的 constructorrendershouldComponentUpdate 等生命周期方法;
  4. 类组件中定义的纯函数(比如事件处理函数)

📦二、 核心依赖解析

依赖类型

项目依赖分为两类:

1. 开发依赖(devDependencies)

仅在开发阶段使用,不会打包到生产环境:

npm i -D vite stylus

image.png

主要包括:

  • vite:构建工具和开发服务器
  • stylus:CSS 预处理器
  • eslint:代码质量检查工具
  • @vitejs/plugin-react:React 插件
当你用 -D 安装包时,npm 会做两件事:
  1. 把包下载到项目的 node_modules 文件夹中(和普通安装一样)。
  2. 将包名和版本号记录到 package.json 的 devDependencies 字段中,而非 dependencies

这样做的好处是:

  • 区分依赖的用途,让项目的依赖结构更清晰。
  • 当其他人协作开发或部署项目时,执行 npm install --production(生产环境安装),只会安装 dependencies 中的包,跳过 devDependencies,减少生产环境的包体积,提升部署效率。

2. 项目依赖(dependencies)

会被打包到生产环境,运行时必需:

image.png

这些是 项目依赖(dependencies) ,也叫生产依赖,会被打包到生产环境中,是应用运行的核心库!

3. 开发依赖(devDependencies)

image.png

这些是 开发依赖(devDependencies) ,只在开发阶段使用,不会被打包到生产环境中。

React 生态核心库

1. React vs React-DOM

React 的架构采用分层设计:

  • react:核心库,负责组件逻辑、状态管理、生命周期等
  • react-dom:DOM 渲染层,负责将 React 组件渲染为浏览器 DOM

💡 类比理解:Vue.js = React(核心) + React-DOM(渲染层)

image.png

2. 关键特性

  • 响应式数据绑定:数据变化自动更新视图
  • 组件化开发:可复用的独立组件
  • 虚拟 DOM:高效的 DOM 更新机制
  • 单向数据流:可预测的状态管理

🏗️三、 项目架构

目录结构

react-demo/
├── node_modules/          # 依赖包
├── public/                # 静态资源
├── src/                   # 源代码
│   ├── assets/            # 静态资源(图片、字体等)
│   ├── pages/             # 页面级组件
│   │   ├── Home.jsx       # 首页
│   │   └── About.jsx      # 关于页
│   ├── router/            # 路由配置
│   │   └── index.jsx      # 路由主文件
│   ├── App.jsx            # 根组件
│   ├── App.css            # 根组件样式
│   ├── main.jsx           # 应用入口
│   ├── index.css          # 全局样式
│   └── index.styl         # Stylus 样式
├── index.html             # HTML 模板
├── package.json           # 项目配置
├── vite.config.js         # Vite 配置
└── README.md              # 项目说明

入口文件:main.jsx

应用的启动文件,负责将 React 组件挂载到 DOM:

import { createRoot } from 'react-dom/client'
import './index.styl'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
  <App />
)

核心流程

  1. 使用 createRoot创建 React 根节点

  2. 选择 DOM 挂载点(#root

  3. 渲染根组件 <App />

2️⃣ API 设计差异

特性ReactVue
创建方式createRoot(容器).render(组件)createApp(组件).mount(容器)
链式调用先创建根再渲染先创建应用再挂载
调用顺序容器 → 组件组件 → 容器
返回值Root 对象App 实例

3️⃣ 核心理念差异

🛣️四、 React 路由配置

安装路由库

npm i react-router-dom

路由类型选择

React Router 提供两种路由模式:

1. BrowserRouter(推荐)

import { BrowserRouter as Router } from 'react-router-dom'
  • 使用 HTML5 History API

  • URL 形式:

    /about /home

  • 更加美观和现代化

  • 需要服务器配置支持

2. HashRouter

import { HashRouter as Router } from 'react-router-dom'
  • 使用 URL hash(#

  • URL 形式:/#/about/#/home

  • 无需服务器配置

  • 兼容老旧浏览器

根组件配置:App.jsx

import {
  BrowserRouter as Router,
  Link
} from 'react-router-dom'
import './App.css'
import AppRoutes from './router/index'
function App() {
  return (
    <Router>
      <nav>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
          <li>
            <Link to="/about">About</Link>
          </li>
        </ul>
      </nav>
      <AppRoutes />
    </Router>
  )
}
export default App

关键点

  • 使用 <Link> 替代 <a>标签,目的是实现无刷新的页面跳转(客户端路由)

    原生 <a href="/xxx"> 标签的行为是:

    1. 点击后会触发浏览器的整页刷新,重新请求服务器获取新页面的 HTML、CSS、JS 等资源。
    2. 这会导致页面闪烁、加载耗时,破坏单页应用(SPA)“无刷新” 的核心特性。

    而 React Router 提供的 <Link> 组件,本质是拦截了点击事件,通过 JavaScript 动态修改浏览器的 URL 和页面内容,不会发起新的网络请求,实现客户端路由的无刷新跳转

  • <Router>组件接管整个应用的路由控制

  • 导航栏和路由视图分离

路由配置文件:router/index.jsx

import {
  Routes,  // 路由容器
  Route,   // 单个路由
} from 'react-router-dom'
import Home from '../pages/Home'
import About from '../pages/About'
export default function RouterConfig() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  )
}

组件说明

  • <Routes> :路由总管,包裹所有路由规则

  • <Route> :定义单个路由映射

    • path :URL 路径

    • element:对应的组件

🔥5、页面组件

About 页简单展示:

const About = () => {
    return (
        <div>
            <h1>About</h1>
        </div>
    )
}
// 模块化输出 esm cjs commonjs
export default About

👉Home 页着重展示:

import {
    // use 开头 函数 hooks 函数
    useState, // 响应式状态管理
    useEffect // 副作用管理 vue中 onMounted 挂载后
} from 'react';
const Home = () => {
    const [repos, setRepos] = useState([]);
    // render 是第一位的
    console.log('组件初始化');
    useEffect(() => {
        // home 组件可以看到了,已经显示
        console.log('挂载后')
        // 发送api 请求,不会和组件渲染去争抢(单线程)
        fetch('https://api.github.com/users/xxx/repos')
            .then(res => res.json())
            .then(data => {
                // console.log(data);
                setRepos(data);
            })
    }, [])
    return (
        <div>
            <h1>Home</h1>
            {
                repos.length ? (
                    <ul>
                        {
                            repos.map(repo => (
                                <li key={repo.id}>
                                    <a href={repo.html_url} target='_blank rel="noopener noreferrer"'>
                                        {repo.name}
                                    </a>
                                </li>
                            ))
                        }
                    </ul>
                ) : (
                    <p>没有数据</p>
                )
            }
        </div>
    )
}
// 模块化输出 esm cjs commonjs
export default Home 

一、useState:让函数组件拥有响应式状态

1. 什么是 useState

useState 是 React 提供的状态钩子,用于在函数组件中定义响应式状态。所谓 “响应式”,就是当状态发生变化时,组件会自动重新渲染,更新页面上的内容。

2. 代码中的用法拆解

// 定义状态 repos,以及修改状态的方法 setRepos,初始值为空数组 []
const [repos, setRepos] = useState([]);

这行代码可以拆成三部分理解:

  • useState([]) :调用 useState 并传入初始值(这里是空数组 []),它会返回一个数组
  • repos:当前的状态值,初始是 [],后续可以通过 setRepos 修改它。
  • setRepos:修改状态的更新函数,调用它可以改变 repos 的值,并且触发组件重新渲染。

3. 在代码中,useState 的作用流程

  1. 组件初始化时,repos 被初始化为空数组 [],所以页面上会先显示 <p>没有数据</p>
  2. 当 fetch 请求获取到 GitHub 仓库数据后,调用 setRepos(data),将 repos 的值更新为接口返回的数组。
  3. 状态更新后,组件会重新渲染,此时 repos.length 为真,页面会渲染仓库列表的 <ul> 和 <li>

4. useState 的核心特点

  • 状态是隔离的:每个组件实例的 useState 状态都是独立的,不会互相影响。

  • 更新函数是异步的(批量更新)setRepos 不会立即改变 repos 的值,React 会批量处理状态更新,再统一触发渲染。

  • 可以定义多个状态:一个组件中可以多次调用 useState,管理不同的状态:

    const [count, setCount] = useState(0);
    const [name, setName] = useState('张三');
    

二、useEffect:处理组件的副作用

1. 什么是 “副作用”?

在 React 中,副作用指的是那些不直接参与组件渲染的操作,常见的有:

  • 网络请求(如 fetch 调用);
  • 操作 DOM(如修改元素样式、添加事件监听);
  • 定时器 / 计数器(setInterval/setTimeout);
  • 订阅 / 取消订阅(如 WebSocket 连接)。

useEffect 就是专门用来处理这些副作用的钩子,它可以模拟类组件的生命周期(如 componentDidMountcomponentDidUpdatecomponentWillUnmount)。

image.png

image.png

🔄六、 开发工作流

Vite 的优势

  1. 智能编译

    • 自动将 Stylus 编译为 CSS
    • 支持 JSX 语法转换
    • TypeScript 开箱即用
  2. 快速热更新

    • 组件级别的热更新
    • 保留应用状态
    • 毫秒级响应
  3. 按需加载

    • 开发时按需编译
    • 生产构建自动优化

🎯七、 最佳实践

1. 组件化思维

  • 将页面拆分为独立、可复用的组件
  • 遵循单一职责原则
  • 使用 JSX 编写组件模板

2. 路由管理

  • 集中管理路由配置 pages目录

  • 使用 Link组件进行导航

3. 样式管理

  • 全局样式:index.css / index.styl
  • 组件样式:与组件同名的 CSS 文件
  • 支持 CSS 预处理器(Stylus、Sass 等)

4. 代码质量

  • 配置 ESLint 进行代码检查
  • 使用 TypeScript 增强类型安全
  • 遵循 React Hooks 最佳实践

📝 总结

本文介绍了基于 Vite 的 React 项目基本框架:

核心要点

  1. 构建工具:Vite 提供极速的开发体验和现代化的构建能力

  2. React 生态

    • react :核心逻辑

    • react-dom :DOM 渲染

    • react-router-dom:路由管理

  3. 项目结构:清晰的目录划分,组件化开发

  4. 路由系统:使用 React Router 实现单页应用导航