【秋招备战】React Router 进化史

Simon Lv2

兄弟,还在为秋招发愁?今天咱们来聊聊 React Router 这个”老司机”。从 v5 的稳重大叔到 v6 的激进青年,再到 v7 的王者归来(直接吞并了 Remix),这个故事比宫斗剧还精彩。系好安全带,咱们发车了!

序章:为什么前端需要路由?

还记得上古时代的网页吗?每点一个链接,整个页面”唰”地白屏,然后慢慢加载。用户体验?不存在的。

然后 SPA(单页应用)横空出世,页面不刷新了,但新问题来了:

  • 浏览器的前进后退按钮废了
  • 刷新页面就 404 了
  • 分享链接?对不起,都是同一个 URL

这时候,前端路由站出来说:”这活儿我来!”

第一章:React Router v5 —— 稳重的老大哥

v5 的基本使用

React Router v5 就像个稳重的老司机,虽然有点啰嗦,但靠谱:

import { 
BrowserRouter as Router,
Route,
Switch,
Link,
useHistory,
useParams
} from 'react-router-dom';

function App() {
return (
<Router>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
<Link to="/user/123">用户详情</Link>
</nav>

{/* Switch 确保只渲染第一个匹配的路由 */}
<Switch>
<Route exact path="/">
<Home />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/user/:id">
<UserDetail />
</Route>
<Route path="*">
<NotFound />
</Route>
</Switch>
</Router>
);
}

// 使用 hooks 获取路由信息
function UserDetail() {
const { id } = useParams(); // 获取路由参数
const history = useHistory(); // 获取 history 对象

const handleGoBack = () => {
history.push('/'); // 编程式导航
};

return (
<div>
<h1>用户 {id} 的详情页</h1>
<button onClick={handleGoBack}>返回首页</button>
</div>
);
}

v5 的高级技巧

// 1. 路由配置化(更优雅的写法)
const routes = [
{ path: '/', component: Home, exact: true },
{ path: '/about', component: About },
{ path: '/user/:id', component: UserDetail }
];

function App() {
return (
<Router>
<Switch>
{routes.map(route => (
<Route
key={route.path}
exact={route.exact}
path={route.path}
component={route.component}
/>
))}
</Switch>
</Router>
);
}

// 2. 路由守卫(类似 Vue Router 的导航守卫)
function PrivateRoute({ children, ...rest }) {
const isAuthenticated = useAuth(); // 假设这是你的认证 hook

return (
<Route {...rest}>
{isAuthenticated ? children : <Redirect to="/login" />}
</Route>
);
}

// 3. 嵌套路由(v5 的痛点之一)
function Users() {
const { path, url } = useRouteMatch();

return (
<div>
<Link to={`${url}/profile`}>个人资料</Link>
<Link to={`${url}/settings`}>设置</Link>

<Switch>
<Route exact path={path}>
<h3>请选择一个选项</h3>
</Route>
<Route path={`${path}/profile`}>
<Profile />
</Route>
<Route path={`${path}/settings`}>
<Settings />
</Route>
</Switch>
</div>
);
}

第二章:React Router v6 —— 激进的革命者

v6 来了,带着破坏性更新,社区炸锅了:”你们是要搞事情吗?”

v6 的巨变

import { 
BrowserRouter,
Routes, // 注意:Switch 改名了
Route,
Link,
useNavigate, // useHistory 改名了
useParams,
Outlet // 新概念:插槽
} from 'react-router-dom';

function App() {
return (
<BrowserRouter>
<Routes>
{/* 不再需要 exact,默认就是精确匹配 */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />

{/* 嵌套路由的新写法,超级优雅! */}
<Route path="/users" element={<Users />}>
<Route index element={<UserList />} />
<Route path=":id" element={<UserDetail />} />
<Route path="settings" element={<Settings />} />
</Route>

{/* 404 页面的新写法 */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}

// 父组件使用 Outlet 渲染子路由
function Users() {
return (
<div>
<h1>用户中心</h1>
<nav>
<Link to="">用户列表</Link>
<Link to="settings">设置</Link>
</nav>

{/* Outlet 就像 Vue 的 router-view */}
<Outlet />
</div>
);
}

// 编程式导航的新写法
function SomeComponent() {
const navigate = useNavigate(); // 不再是 history.push

const handleClick = () => {
navigate('/about'); // 更直观
navigate(-1); // 后退
navigate('/user/123', { replace: true }); // 替换当前记录
};

return <button onClick={handleClick}>走你!</button>;
}

v6 的杀手锏功能

// 1. 相对路由(终于不用拼接路径了!)
function UserProfile() {
return (
<div>
{/* 相对于当前路由 */}
<Link to="..">返回上级</Link>
<Link to="../settings">设置</Link>
<Link to="edit">编辑</Link>
</div>
);
}

// 2. 路由配置对象(更接近 Vue Router)
const routes = [
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{ path: 'about', element: <About /> },
{
path: 'users',
element: <Users />,
children: [
{ index: true, element: <UserList /> },
{ path: ':id', element: <UserDetail /> }
]
}
]
}
];

// 使用 useRoutes hook
function App() {
const element = useRoutes(routes);
return element;
}

// 3. 懒加载(Suspense 集成)
const LazyAbout = React.lazy(() => import('./About'));

<Route
path="/about"
element={
<React.Suspense fallback={<Loading />}>
<LazyAbout />
</React.Suspense>
}
/>

第三章:底层原理 —— 路由的魔法是怎么实现的?

来,让我们扒开 React Router 的外衣,看看它的真面目!

History API:一切的基础

// 浏览器提供的 History API
window.history.pushState(state, title, url); // 添加历史记录
window.history.replaceState(state, title, url); // 替换当前记录
window.history.back(); // 后退
window.history.forward(); // 前进

// 监听路由变化
window.addEventListener('popstate', (event) => {
console.log('路由变化了!', location.pathname);
});

简化版 React Router 实现

让我们自己造个轮子,你就明白了:

// 1. 创建 Router Context
const RouterContext = React.createContext();

// 2. BrowserRouter 实现
function BrowserRouter({ children }) {
const [location, setLocation] = useState(window.location.pathname);

useEffect(() => {
// 监听浏览器前进后退
const handlePopState = () => {
setLocation(window.location.pathname);
};

window.addEventListener('popstate', handlePopState);
return () => window.removeEventListener('popstate', handlePopState);
}, []);

const navigate = (to, options = {}) => {
if (options.replace) {
window.history.replaceState(null, '', to);
} else {
window.history.pushState(null, '', to);
}
setLocation(to);
};

return (
<RouterContext.Provider value={{ location, navigate }}>
{children}
</RouterContext.Provider>
);
}

// 3. Route 组件实现
function Route({ path, element }) {
const { location } = useContext(RouterContext);

// 简化版路径匹配(真实的要复杂得多)
const match = matchPath(path, location);

return match ? element : null;
}

// 4. Link 组件实现
function Link({ to, children }) {
const { navigate } = useContext(RouterContext);

const handleClick = (e) => {
e.preventDefault(); // 阻止默认跳转
navigate(to);
};

return <a href={to} onClick={handleClick}>{children}</a>;
}

// 5. 路径匹配算法(简化版)
function matchPath(pattern, pathname) {
// 处理动态路由参数
// /user/:id => /user/123
const regexPattern = pattern
.replace(/:[^/]+/g, '([^/]+)') // 替换 :id 为正则
.replace(/\*/g, '.*'); // 处理通配符

const regex = new RegExp(`^${regexPattern}$`);
return regex.test(pathname);
}

Hash Router vs Browser Router

// Hash Router:使用 URL 的 hash 部分
// http://example.com/#/about
class HashRouter {
constructor() {
window.addEventListener('hashchange', this.handleHashChange);
}

handleHashChange = () => {
const path = window.location.hash.slice(1); // 去掉 #
this.updateView(path);
}

push(path) {
window.location.hash = path;
}
}

// Browser Router:使用 HTML5 History API
// http://example.com/about
class BrowserRouter {
constructor() {
window.addEventListener('popstate', this.handlePopState);
}

handlePopState = () => {
this.updateView(window.location.pathname);
}

push(path) {
window.history.pushState(null, '', path);
this.updateView(path);
}
}

面试加分点

  • Hash Router 兼容性好,但 URL 有个丑陋的 #
  • Browser Router 需要服务器配置支持(所有路由都返回 index.html)
  • Hash Router 不会发送到服务器,Browser Router 会

第四章:React Router v7 —— 王者归来(融合 Remix)

2024 年底,React Router v7 震撼发布,直接把 Remix 给”吞并”了!

v7 的重磅特性:SSR 支持

// v7 最大的变化:原生支持 SSR!
// 这不是简单的路由了,这是全栈框架!

// 1. 数据加载(类似 Next.js 的 getServerSideProps)
export async function loader({ params }) {
const user = await fetchUser(params.id);
return { user };
}

export default function UserProfile() {
const { user } = useLoaderData();

return <h1>欢迎,{user.name}!</h1>;
}

// 2. Action 处理表单提交
export async function action({ request }) {
const formData = await request.formData();
const email = formData.get('email');

await updateEmail(email);
return redirect('/profile');
}

export default function Settings() {
return (
<Form method="post">
<input name="email" type="email" />
<button type="submit">更新邮箱</button>
</Form>
);
}

// 3. 错误边界
export function ErrorBoundary() {
const error = useRouteError();

return (
<div>
<h1>哎呀,出错了!</h1>
<p>{error.message}</p>
</div>
);
}

v7 的完整 SSR 示例

// app/routes/blog.$slug.jsx
import { useLoaderData } from 'react-router-dom';

// 服务器端数据获取
export async function loader({ params }) {
// 这段代码只在服务器运行!
const post = await db.post.findUnique({
where: { slug: params.slug }
});

if (!post) {
throw new Response('Not Found', { status: 404 });
}

return { post };
}

// Meta 标签(SEO 优化)
export function meta({ data }) {
return [
{ title: data.post.title },
{ name: 'description', content: data.post.excerpt },
{ property: 'og:title', content: data.post.title }
];
}

// 组件
export default function BlogPost() {
const { post } = useLoaderData();

return (
<article>
<h1>{post.title}</h1>
<time>{post.publishedAt}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}

// 流式 SSR(React 18 特性)
export async function loader() {
return defer({
critical: await getCriticalData(), // 立即加载
comments: getComments() // 延迟加载,流式传输
});
}

export default function Post() {
const { critical, comments } = useLoaderData();

return (
<div>
<h1>{critical.title}</h1>
<Suspense fallback={<LoadingComments />}>
<Await resolve={comments}>
{(comments) => <Comments data={comments} />}
</Await>
</Suspense>
</div>
);
}

第五章:版本迁移指南 —— 从 v5 到 v6/v7

主要变化对照表

特性 v5 v6/v7
路由容器 <Switch> <Routes>
路由组件 component={Component} element={<Component />}
嵌套路由 手动拼接路径 自动处理,使用 <Outlet>
导航 Hook useHistory useNavigate
路由匹配 需要 exact 默认精确匹配
相对链接 不支持 完全支持

迁移示例

// v5 老代码
function OldApp() {
const history = useHistory();

return (
<Switch>
<Route exact path="/" component={Home} />
<Route path="/users/:id" component={UserDetail} />
</Switch>
);
}

// v6/v7 新代码
function NewApp() {
const navigate = useNavigate();

return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
);
}

// 复杂嵌套路由迁移
// v5:需要在每个组件里处理
function Users() {
const { path, url } = useRouteMatch();
return (
<Switch>
<Route exact path={path} component={UserList} />
<Route path={`${path}/:id`} component={UserDetail} />
</Switch>
);
}

// v6/v7:集中配置
<Route path="/users" element={<Users />}>
<Route index element={<UserList />} />
<Route path=":id" element={<UserDetail />} />
</Route>

第六章:实战技巧与性能优化

1. 路由懒加载

// 配合 React.lazy 和 Suspense
const LazyDashboard = lazy(() =>
import(/* webpackChunkName: "dashboard" */ './Dashboard')
);

function App() {
return (
<Routes>
<Route
path="/dashboard/*"
element={
<Suspense fallback={<Loading />}>
<LazyDashboard />
</Suspense>
}
/>
</Routes>
);
}

2. 路由预加载

// 鼠标悬停时预加载
function PreloadableLink({ to, children }) {
const handleMouseEnter = () => {
// 预加载组件
import(`./pages${to}`);
};

return (
<Link to={to} onMouseEnter={handleMouseEnter}>
{children}
</Link>
);
}

3. 路由过渡动画

import { CSSTransition, TransitionGroup } from 'react-transition-group';

function AnimatedRoutes() {
const location = useLocation();

return (
<TransitionGroup>
<CSSTransition
key={location.pathname}
timeout={300}
classNames="fade"
>
<Routes location={location}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</CSSTransition>
</TransitionGroup>
);
}

/* CSS */
.fade-enter {
opacity: 0;
}
.fade-enter-active {
opacity: 1;
transition: opacity 300ms;
}
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: opacity 300ms;
}

4. 路由级别的状态管理

// 使用 location.state 传递临时数据
function ProductList() {
const navigate = useNavigate();

const handleEdit = (product) => {
navigate('/edit', {
state: { product, from: '/products' }
});
};
}

function EditProduct() {
const location = useLocation();
const navigate = useNavigate();
const product = location.state?.product;

const handleSave = async (data) => {
await saveProduct(data);
// 返回来源页面
navigate(location.state?.from || '/');
};
}

第七章:面试必考题

面试官:React Router 的原理是什么?

标准答案: React Router 基于浏览器的 History API,通过监听 URL 变化来渲染对应的组件。核心原理:

  1. 使用 Context 在组件树中传递路由状态
  2. Route 组件根据当前 URL 匹配 path 来决定是否渲染
  3. Link 组件阻止默认行为,调用 history.pushState 更新 URL
  4. 监听 popstate 事件处理浏览器前进后退

面试官:如何实现路由守卫?

// v6/v7 的实现方式
function RequireAuth({ children }) {
const auth = useAuth();
const location = useLocation();

if (!auth.user) {
// 记录用户想去的页面,登录后跳转
return <Navigate to="/login" state={{ from: location }} replace />;
}

return children;
}

// 使用
<Route
path="/protected"
element={
<RequireAuth>
<ProtectedPage />
</RequireAuth>
}
/>

面试官:Hash Router 和 Browser Router 的区别?

加分回答

  • Hash Router:URL 带 #,不需要服务器配置,不支持 SSR
  • Browser Router:更美观的 URL,需要服务器配置,支持 SSR
  • Memory Router:URL 不变化,用于 React Native 或测试
  • Static Router:用于 SSR,不会改变 URL

面试官:React Router v6 为什么要做 Breaking Changes?

高情商回答: v6 的改动虽然很大,但带来了:

  1. 更小的包体积(减少约 50%)
  2. 更好的 TypeScript 支持
  3. 更直观的嵌套路由
  4. 相对路径支持
  5. 为 SSR 做准备(v7 实现了)

结语:路由的未来

从 v5 的稳重,到 v6 的激进,再到 v7 的全栈化,React Router 的进化史就是前端发展的缩影。记住:

  1. v5 还在广泛使用,很多老项目没有升级
  2. v6 是过渡,新项目建议直接上
  3. v7 是未来,SSR 成为标配

最后给秋招的你一些建议:

  • 面试时能讲清楚版本差异会很加分
  • 理解原理比记 API 重要 100 倍
  • 如果面试官问到 v7,说明这是家技术很新的公司
  • 准备一个路由相关的项目亮点,比如”我用路由懒加载优化了 70% 的首屏加载时间”

记住:技术是在不断进化的,保持学习的心态比掌握某个版本的 API 更重要。今天的 v7,可能就是明天的 “legacy code”。


P.S. 如果这篇文章帮到了你,记得收藏起来。毕竟,面试前临时抱佛脚的时候,你会感谢现在认真看完的自己的!

P.P.S. React Router 团队:求求你们,别再搞破坏性更新了,我们学不动了… 😭

  • 标题: 【秋招备战】React Router 进化史
  • 作者: Simon
  • 创建于 : 2025-08-13 15:53:07
  • 更新于 : 2025-08-13 15:56:36
  • 链接: https://www.simonicle.cn/2025/08/13/【秋招备战】React Router 进化史/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论