从零开始,用 React 构建你的第一个前端应用:像搭乐高一样写网页
如果你刚刚接触前端开发,看到“React”、“组件”、“路由”这些词可能会感到有些晕头转向。别担心!这篇文章将带你一步一步地理解一个真实 React 项目的结构和运行逻辑。我们会像搭乐高积木一样,把网页拆成一块块小零件,再组合起来,最终做出一个能跑、能看、还能交互的小网站。
🧩 一、React 是什么?为什么它像“乐高”?
React 是一个由 Facebook(现 Meta)开发和维护的开源 JavaScript 库,主要用于构建用户界面,特别是单页应用(SPA)中的组件化 UI。它于 2013 年首次发布,如今已成为前端开发中最流行和广泛使用的工具之一。
React 的核心特点:
“组件化” —— 把网页也拆成一个个独立的小模块(组件),每个模块负责自己那一小块功能和界面。
比如:
- 顶部导航栏是一个组件;
- 主体内容区是一个组件;
- 底部版权信息又是一个组件。
这些组件可以互相嵌套、复用,甚至在不同页面之间共享。这不仅让代码更清晰,也更容易维护和扩展。
而我们今天要分析的这个项目,就是一个典型的 React 小应用:它有两个页面——首页(Home)和关于页(About),通过点击导航链接切换,首页还会自动从 GitHub 拉取用户的仓库列表并展示出来。
听起来是不是很酷?那我们就从最外层开始,一层层“剥开”这个项目。
📦 二、 创建项目及依赖介绍
在你的电脑终端(Terminal)中,输入:
npm create vite@latest react-demo -- --template react
或者简写为:
npm init vite react-demo
然后按照提示选择 React + JavaScript(或其他你喜欢的语言),Vite 就会自动为你生成一个完整的 React 项目骨架,名字就叫 react-demo。
稍等几秒,你会看到类似这样的目录结构:
react-demo/
├── node_modules/ # 项目依赖包(安装后自动生成)
├── public/ # 静态资源目录(如 favicon.ico)
├── src/ # 源码目录(你主要写代码的地方)
│ ├── assets/ # 图片、字体等资源文件
│ ├── pages/ # 页面组件(我们自己组织的)
│ │ ├── About.jsx
│ │ └── Home.jsx
│ ├── router/ # 路由配置
│ ├── App.css
│ ├── App.jsx
│ ├── index.css / index.styl
│ └── main.jsx
├── index.html
├── package.json # ← 重点!项目的“户口本”
└── README.md
这个结构看起来有点多,但别慌——我们真正需要关注的核心文件其实就那么几个。而其中最关键的“说明书”,就是 package.json。
打开 package.json,你会看到类似这样的内容(已简化):
{
"name": "react-demo",
"version": "0.1.0",
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.10.1"
},
"devDependencies": {
"vite": "^5.0.0",
"stylus": "^0.63.0"
}
}
这里列出了两类依赖:
dependencies:项目运行时必须用到的库(上线后也需要);devDependencies:只在开发阶段使用的工具(比如构建、编译、格式检查等)。
我们先聚焦最核心的两个生产依赖:react 和 react-dom。
🔹 react:负责“思考”的大脑
react 是整个框架的核心。它提供了:
- 定义组件的能力(比如
function App() { ... }); - 状态管理(
useState); - 副作用处理(
useEffect); - JSX 语法支持(让你能用
<div>Hello</div>这种写法)。
你可以把它想象成设计师——他画出房子的蓝图(UI 结构),规划每个房间的功能(交互逻辑),但他不会亲自搬砖砌墙。
🔹 react-dom:负责“建造”的施工队
react-dom 则是 React 在浏览器环境中的具体实现。它的唯一使命就是:把 React 组件变成真实的 HTML 元素,并插入到页面中。
在 main.jsx 中,我们这样使用它:
import { createRoot } from 'react-dom/client'
createRoot(document.getElementById('root')).render(<App />)
这行代码的意思是:“请 react-dom 把 <App /> 这个 React 组件,渲染到 HTML 中 id="root" 的位置。”
💡 关键区别:
react只管“描述 UI 应该长什么样”;react-dom负责“把这个描述变成浏览器能显示的真实 DOM”。
它们就像一对搭档:一个出方案,一个干实事。缺一不可。
✅ 小知识:如果你将来做服务端渲染(SSR)或 React Native(移动端),就会用到
react-server-dom或react-native,而不是react-dom。但只要是在普通网页里,就必须用react-dom。
🏗️ 三、项目的骨架:index.html 和 main.jsx
1. index.html:网页的“空盒子”
打开 index.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>react-demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
实际上,在 Vite(一个现代前端构建工具)项目中,index.html 通常只包含一个空的 <div id="root"></div> 容器。你可以把它想象成一个空的展示柜——里面什么都没有,但等着 React 把内容“放进去”。
这个 <div id="root"> 就是 React 应用的“入口点”。所有 React 组件最终都会被渲染到这个盒子里。
同时<script type="module" src="/src/main.jsx"></script> 借助 Vite 在开发时将 React 入口文件按 ES 模块加载,并自动编译 JSX 为浏览器可执行的 JavaScript。
2. main.jsx:启动引擎
接下来是 main.jsx,它是整个应用的“启动按钮”:
import { createRoot } from 'react-dom/client'
import './index.styl'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(<App />)
这段代码做了三件事:
- 引入
createRoot:这是 React 18+ 的新 API,用来创建一个“根容器”,负责管理整个应用的渲染。 - 引入全局样式
index.styl:使用了 Stylus 预处理器写的 CSS,让页面看起来更美观。 - 挂载
App组件:把<App />这个“主组件”塞进id="root"的盒子里。
🏠 三、主组件 App.jsx:房子的“户型图”
现在我们进入 src/App.jsx,这是整个应用的“户型图”——它决定了房间里有哪些区域,以及怎么切换。
import { BrowserRouter as Router } from 'react-router-dom'
import './App.css';
import AppRoutes from './router/index'
function App() {
return (
<Router>
<nav>
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/about">关于</Link></li>
</ul>
</nav>
<AppRoutes />
</Router>
)
}
export default App
关键角色解析:
<Router>:来自react-router-dom,它是“导航系统”。没有它,点击链接只会刷新整个页面;有了它,页面可以在不刷新的情况下切换内容(就像手机 App 切换页面一样流畅)。<Link>:代替传统的<a>标签。它不会跳转新页面,而是告诉 Router:“我想去/或/about”。<AppRoutes />:这是我们自定义的路由配置组件,决定“哪个 URL 对应哪个页面”。
💡
App.jsx就像你家的客厅 + 走廊。走廊上有两扇门,一扇标着“卧室”(首页),一扇标着“书房”(关于页)。<Router>是智能门禁系统,你一推门,它就知道该打开哪间房,还不用重新装修整栋房子。
🗺️ 四、路由配置 router/index.jsx:房间分配表
路由的作用是“根据 URL 显示对应的页面”。来看 router/index.jsx:
import { Routes, Route } from 'react-router-dom';
import Home from '../pages/Home'
import About from '../pages/About'
export default function AppRoutes() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
)
}
<Routes>:路由的“总管”,负责监听当前 URL。<Route>:每一条规则,比如当路径是/时,显示<Home />组件。
✅ 注意:
element={<Home />}里的花括号表示“这里要放一个 React 元素”,而不是字符串。
整个 AppRoutes 函数组件的核心作用,是将 URL 路径与页面组件建立映射关系,并以可复用的方式封装起来。通过 export default 导出后,主应用(如 App.jsx)只需引入 <AppRoutes /> 并直接使用,就能实现完整的页面切换功能。这不仅让路由逻辑集中、清晰,也符合 React “组件化”的思想——把复杂功能拆成独立、可维护的小单元
📄 五、页面组件:Home.jsx 和 About.jsx
1. About.jsx:最简单的页面
const About = () => {
return (
<div>
<h1>About</h1>
</div>
)
}
export default About
这就是一个“静态页面”——只显示一行字。没有任何复杂逻辑。
2. Home.jsx:会“动”的智能页面
这才是重点!Home.jsx 不仅显示内容,还会自动从网上拉数据:
// 导入依赖
import { useState, useEffect } from 'react'
// 组件定义
const Home = () => {
const [repos, setRepos] = useState([]);
console.log('组件初始化');// 每次组件渲染时都会执行(包括初始渲染和后续更新)
useEffect(() => { // 副作用处理 (数据获取)
console.log('组件挂载后');
fetch('https://api.github.com/users/xxx/repos')
.then(res => res.json())
.then(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="noreferrer">
{repo.name}
</a>
</li>
)) }
</ul>
) : (
<p>暂无仓库</p>
) }
</div>
)
}
export default Home;
🔍 分步解读:
(1)useState:给组件“装内存”(用于在函数组件中管理状态)
const [repos, setRepos] = useState([]);
repos:当前存储的仓库列表(初始为空数组)。setRepos:一个函数,用来更新repos。
(2)useEffect:组件的“生命周期钩子”(用于处理副作用)
useEffect(() => {
// 发请求
}, []);
useEffect接收两个参数:回调函数和依赖数组- 空依赖数组
[]表示这个 effect 只在组件挂载后执行一次(类似componentDidMount) - 获取 GitHub 用户 "shunwuyu" 的仓库数据
- 数据获取成功后,通过
setRepos(data)更新状态
⚠️ 注意:
fetch是浏览器原生 API,用于发送网络请求。它返回一个 Promise,所以要用.then()处理结果。
(3)条件渲染:有数据就列清单,没数据就提示
{ repos.length ? (
<ul>...</ul>
) : (
<p>暂无仓库</p>
) }
这是 React 中常见的“三元表达式”写法:如果 repos 有内容,就渲染列表;否则显示提示文字。
(4)map 渲染列表
{ repos.map(repo => (
<li key={repo.id}>
<a href={repo.html_url}>{repo.name}</a>
</li>
)) }
map把数组变成一组 JSX 元素。key={repo.id}是 React 的要求:每个列表项必须有唯一标识,方便高效更新。rel="noreferrer"防止安全漏洞
🎨 六、样式与构建:index.styl 和 Vite
项目中使用了 index.styl 作为全局样式文件:
import './index.styl' // in main.jsx
Stylus 是一种 CSS 预处理器,支持变量、嵌套等高级语法,写起来更简洁。虽然你没提供具体内容,但可以想象它定义了导航栏颜色、字体、间距等。
而整个项目是用 Vite 搭建的:
npm create vite@latest创建项目;npm run dev启动开发服务器;- 支持热更新(改代码,浏览器自动刷新);
- 极速冷启动(秒开,不像老工具要等半分钟)。
💡 Vite vs Webpack:
如果 Webpack 是一辆重型卡车(功能强但启动慢),Vite 就是电动滑板车——轻快、敏捷,特别适合现代开发。
🔄 七、数据流动:从 API 到屏幕的全过程
让我们串一遍首页的数据流:
- 用户访问
/→ Router 匹配到<Home />; - React 开始渲染
Home组件; - 执行
useState([]),repos初始化为空; - 组件首次渲染完成,触发
useEffect; fetch向https://api.github.com/users/shunwuyu/repos发起请求;- 收到 JSON 数据后,调用
setRepos(data); - React 检测到
repos变化,自动重新渲染组件; - 页面从“暂无仓库”变成一个带链接的仓库列表。
✅ 这就是 React 的“响应式”魅力:数据变了,视图自动更新,你不需要手动操作 DOM。
🧪 八、StrictMode:React 的“代码体检医生”
🔍 它是什么?
StrictMode 不是功能组件,也不渲染任何 UI。它更像一位严谨的代码审查员,只在开发环境中运行,默默帮你发现潜在问题:
- 警告使用了即将废弃的 API(如旧版生命周期方法);
- 检测不安全的副作用(比如在函数组件主体中直接操作 DOM);
- 故意对某些函数进行双重调用(如
useState初始化函数、useEffect回调),以暴露依赖缺失或非幂等逻辑,帮助你写出更健壮、可预测的代码。
💡 举个例子:如果你在
useEffect里忘了加依赖数组,StrictMode可能会让副作用执行两次,从而暴露出“数据重复请求”或“状态异常”等问题——这正是它“提前暴露 bug”的设计哲学。
⚙️ 如何启用?
只需包裹你的根组件:
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>
)
🧭 九、路由的两种形式:BrowserRouter vs HashRouter
在 App.jsx 中,我们用了:
import { BrowserRouter as Router } from 'react-router-dom'
BrowserRouter:使用 HTML5 History API,URL 看起来干净,如http://localhost:3000/about。HashRouter:URL 带#,如http://localhost:3000/#/about,兼容老浏览器。
现代项目基本都用 BrowserRouter,除非你要部署到不支持 History API 的静态服务器。
🌟 十、总结:React 项目的“心法”
通过这个小项目,我们掌握了 React 开发的核心脉络:
| 概念 | 作用 | 类比 |
|---|---|---|
| 组件(Component) | UI 的基本单元 | 乐高积木 |
| 状态(State) | 组件的记忆 | 笔记本 |
| 副作用(useEffect) | 处理外部交互(如 API) | 开机启动程序 |
| 路由(Router) | 页面切换 | 房间门牌系统 |
| JSX | 用 JS 写 HTML | 混合语言说明书 |
| 响应式更新 | 数据变 → 视图自动变 | 智能镜子 |
✅ 记住一句话:
React 不是直接操作网页,而是描述“在某种状态下,页面应该长什么样”。剩下的,交给 React 自己去算。
💬 结语:编程不是魔法,而是逻辑的艺术
很多人觉得前端开发很神秘,其实它就像搭积木、写菜谱、画流程图——把复杂问题拆解成小步骤,再用代码描述清楚。
React 的伟大之处,在于它用“组件 + 状态”的模型,让这种拆解变得直观而优雅。你不需要记住所有 API,只要理解“数据如何流动”、“界面如何响应”,就能写出健壮的应用。
希望这篇文章让你对 React 有了温暖而清晰的认识。下次当你看到一个动态网页时,不妨想一想:它的背后,是不是也藏着一个像 Home.jsx 这样会“思考”的小精灵呢?
动手吧!最好的学习,永远是从写下第一行代码开始。