一、项目初始化:用 Vite 快速启动 React 应用
Vite 是新一代前端构建工具,以 极速冷启动 和 按需编译 著称,非常适合现代 React 开发。
bash
编辑
# 初始化项目(选择 React + JavaScript 模板)
npm create vite@latest my-react-app -- --template react
cd my-react-app
# 安装依赖
npm install
# 安装路由和样式预处理器
npm install react-router-dom
npm install -D stylus # -D 表示开发依赖(devDependencies)
✅ 为什么选 Vite?
相比 Create React App(CRA),Vite 启动快 10 倍以上,且原生支持 ESM(ES Module),无需打包即可运行,极大提升开发体验。
二、目录结构设计(推荐规范)
text
编辑
my-react-app/
├── src/
│ ├── components/ # 可复用组件(如 Header、Card)
│ ├── pages/ # 页面级组件(Home、About)
│ ├── router/ # 路由配置
│ ├── App.jsx # 根组件
│ └── main.jsx # 入口文件
├── public/
└── index.html
📌 约定优于配置:将
Home和About放入pages/目录,清晰区分“页面”与“组件”。
三、核心功能实现
1. 页面组件:Home(拉取 GitHub 仓库)
jsx
编辑
// src/pages/Home.jsx
import { useState, useEffect } from "react";
const Home = () => {
const [repos, setRepos] = useState([]);
// 组件挂载后发起请求(等效 Vue 的 onMounted)
useEffect(() => {
fetch('https://api.github.com/users/shunwuyu/repos')
.then(res => res.json())
.then(data => setRepos(data))
.catch(err => console.error('Failed to fetch repos:', err));
}, []); // 空依赖数组 = 仅执行一次
return (
<div>
<h1>My GitHub Repos</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>Loading repos...</p>
)}
</div>
);
};
export default Home;
⚠️ 注意事项:
rel="noreferrer"防止安全漏洞(避免泄露 referrer 信息)- 添加
.catch()处理网络错误,提升健壮性
2. About 页面(静态内容)
jsx
编辑
// src/pages/About.jsx
const About = () => {
return (
<div>
<h1>About Me</h1>
<p>This is a demo app built with React and Vite.</p>
</div>
);
};
export default About;
3. 路由配置:集中管理路由规则
🔑 关键理解: 不是“占位符”,而是“路由分发器”
在你的项目中:
jsx
编辑
// src/App.jsx
import AppRoutes from './router';
function App() {
return (
<Router>
<nav>...</nav>
<AppRoutes /> {/* ← 这不是空占位符! */}
</Router>
);
}
<AppRoutes /> 并非一个简单的 DOM 占位元素(如 <div></div>),而是一个自定义功能组件,其核心作用如下:
一、先明确:它不是 “空占位符”,是自定义组件的调用
<AppRoutes /> 是你从 ./router 文件导入的自定义组件(通常文件名为 router.jsx 或 AppRoutes.jsx)。它的本质是把分散的路由规则抽离到独立文件中,让 App 组件保持简洁、专注 UI 结构。
二、核心作用(3 个关键)
1. 承载路由规则,实现 “路由表集中管理”
jsx
编辑
// src/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>是 React Router 的路由容器,只渲染当前 URL 匹配的<Route>;<AppRoutes />在App中被调用,相当于将整套路由逻辑“注入”到应用结构中。
2. 渲染 “当前 URL 匹配的页面组件”
- 当 URL 为
/→ 渲染<Home /> - 当 URL 为
/about→ 渲染<About />
👉 因此,<AppRoutes /> 是页面内容的“动态渲染区” :
导航栏(<nav>)固定不变,而 <AppRoutes /> 所在区域会根据 URL 动态替换为对应的页面组件。
3. 解耦路由规则,提升可维护性
若将所有 <Route> 写在 App.jsx 中,随着页面增多(如 /user, /detail, /settings),App 会迅速膨胀。
通过抽离为 AppRoutes:
- ✅ UI 与路由逻辑分离:
App只管布局,AppRoutes只管路由; - ✅ 路由表独立维护:新增页面只需修改
router/index.jsx,无需触碰App; - ✅ 团队协作更清晰:前端可并行开发页面与路由配置。
三、对比 “占位符”:它是 “功能组件” 而非 “空占位”
| 特征 | 单纯占位符(如 <div></div>) | <AppRoutes /> |
|---|---|---|
| 是否有逻辑 | ❌ 无,仅占 DOM 位置 | ✅ 有,包含路由匹配与渲染逻辑 |
| 是否动态渲染内容 | ❌ 内容固定 | ✅ 内容随 URL 变化 |
| 是否可复用 / 维护 | ❌ 无复用价值 | ✅ 路由规则集中,高内聚易维护 |
💡 一句话总结:
<AppRoutes />是 “路由分发器” —— 占位符只占位置,而它会根据 URL 动态渲染不同内容。
4. 根组件 App:集成导航与路由分发器
jsx
编辑
// src/App.jsx
import { BrowserRouter as Router, Link } from 'react-router-dom';
import './App.css';
import AppRoutes from './router'; // ← 导入路由分发器
function App() {
return (
<Router>
<nav style={{ marginBottom: '20px' }}>
<ul style={{ listStyle: 'none', display: 'flex', gap: '16px' }}>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
</ul>
</nav>
<AppRoutes /> {/* ← 路由内容在此动态渲染 */}
</Router>
);
}
export default App;
💡 为什么用
<Link>而不是<a>?
<Link>是react-router-dom提供的组件,点击时不会刷新页面,而是通过 History API 实现前端路由跳转,保持 SPA 体验。
5. 入口文件:挂载到 DOM
jsx
编辑
// src/main.jsx
import { createRoot } from 'react-dom/client';
import './index.styl'; // 全局样式(Stylus)
import App from './App';
createRoot(document.getElementById('root')).render(<App />);
🎨 Stylus 示例(src/index.styl) :
stylus 编辑 * margin 0 padding 0 box-sizing border-box body font-family -apple-system, sans-serif
四、Vue 对比视角:<router-view> 同样不是占位符!
很多开发者会疑惑:在 Vue 中,类似 <AppRoutes /> 的东西是不是占位符?
答案是否定的。
Vue 中的对应物:<router-view />
vue
编辑
<!-- App.vue -->
<template>
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
</nav>
<router-view /> <!-- ← Vue 的“路由出口” -->
</template>
| 维度 | 单纯占位符(如 <div></div>) | Vue 的 <router-view /> |
|---|---|---|
| 核心作用 | 仅占 DOM 位置 | 承载路由匹配逻辑,动态渲染组件 |
| 内容是否动态 | ❌ 固定 | ✅ 随 URL 变化 |
| 是否依赖路由表 | ❌ 否 | ✅ 完全依赖 routes 配置 |
| 扩展能力 | ❌ 无 | ✅ 支持嵌套路由、过渡动画、路由守卫等 |
✅ 结论:
无论是 React 的<AppRoutes />,还是 Vue 的<router-view />,它们都是带核心路由功能的“动态渲染出口” ,绝非“占位符”。
真正的占位符:只有那些无逻辑、内容固定、仅用于布局的空标签(如<div></div>)。
💡 记忆口诀:
“凡承载路由动态渲染者,皆非占位符。”
五、启动与验证
bash
编辑
npm run dev
访问 http://localhost:5173(Vite 默认端口),你应该看到:
- 顶部导航栏可点击切换 Home / About
- Home 页自动加载 GitHub 仓库列表(需联网)
- 点击仓库名在新标签页打开 GitHub 页面
六、总结要点 ✅
| 模块 | 关键点 |
|---|---|
| 项目初始化 | npm create vite + 选 React 模板 |
| 状态管理 | useState 存数据,useEffect 发请求 |
| 路由分发器 | <AppRoutes /> 是路由规则容器 + 动态渲染出口 |
| 导航 | 用 <Link> 实现无刷新跳转 |
| 样式 | Stylus 预处理器(需安装) |
| 模块导出 | 每个文件只能有一个 export default |
七、拓展思考 🔍
Q1:路由方案对比
| 方案 | 特点 | 适用场景 |
|---|---|---|
BrowserRouter | URL 美观(/about),需后端支持 history fallback | 现代浏览器,有后端配合 |
HashRouter | URL 含 #(/#/about),无需后端配置 | 纯静态部署(如 GitHub Pages) |
你的项目若部署在 GitHub Pages,建议改用
HashRouter。
Q2:React 18+ 的 Strict Mode 为何执行两次?
这是 React 的开发模式行为,用于检测副作用是否被正确封装(如是否在 useEffect 中)。生产环境只执行一次,无需担心。
八、常见问题排查
- 白屏无内容? → 检查是否忘记在
App中引入AppRoutes - 路由不生效? → 确保
Router包裹了所有路由相关组件 - Stylus 不编译? → 确认已安装
stylus并重启 Vite - GitHub API 限流? → 未认证请求每小时最多 60 次,可加 token 提升限额