说说 React Router 中有几种模式?实现原理?
问题解析(面试官考察点)
面试官通过此问题主要考察:
- 对 React Router 两种路由模式的掌握程度
- 对前端路由实现原理的深入理解
- 对浏览器 History API 和 hash 机制的了解
- 能够根据场景选择合适的路由模式
核心概念(基础知识点)
前端路由的核心需求
在单页应用(SPA)中,前端路由需要满足:
- 改变 URL 且不让浏览器向服务器发送请求
- 在不刷新页面的前提下动态改变浏览器地址栏中的 URL
- 能够监听 URL 变化并做出响应
两种路由模式
React Router 主要提供了两种路由模式:
- Hash 模式: URL 中包含
#,如http://example.com/#/page - History 模式: 使用 HTML5 History API,URL 如
http://example.com/page
详细解答(代码示例)
Hash 模式
Hash 模式利用 URL 中的 hash(#)部分来实现路由:
import { HashRouter as Router, Route, Link } from "react-router-dom";
function App() {
return (
<Router>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
</nav>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Router>
);
}
特点:
- URL 示例:
http://example.com/#/home - hash 值的改变不会触发页面刷新
- hash 值改变会触发
hashchange事件 - 兼容性好(IE8+)
History 模式
History 模式使用 HTML5 的 History API:
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
function App() {
return (
<Router>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
</nav>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Router>
);
}
特点:
- URL 示例:
http://example.com/home - 使用
pushState/replaceState方法改变 URL - 需要服务端配置支持(处理 404)
- URL 更美观
手动实现简易 HashRouter
import React, { Component } from "react";
// 创建 Context
const RouterContext = React.createContext();
class HashRouter extends Component {
constructor(props) {
super(props);
this.state = {
location: {
pathname: window.location.hash.slice(1) || "/"
}
};
}
componentDidMount() {
// 设置默认 hash
window.location.hash = window.location.hash || "/";
// 监听 hash 变化
window.addEventListener("hashchange", () => {
this.setState({
location: {
pathname: window.location.hash.slice(1) || "/"
}
});
});
}
render() {
const value = {
location: this.state.location,
history: {
push: (path) => {
window.location.hash = path;
}
}
};
return (
<RouterContext.Provider value={value}>
{this.props.children}
</RouterContext.Provider>
);
}
}
// Route 组件实现
class Route extends Component {
render() {
return (
<RouterContext.Consumer>
{(context) => {
const { path, component: Component } = this.props;
const pathname = context.location.pathname;
// 简单匹配
const match = pathname === path ||
(path !== "/" && pathname.startsWith(path));
return match ? <Component /> : null;
}}
</RouterContext.Consumer>
);
}
}
// Link 组件实现
class Link extends Component {
handleClick = (e) => {
e.preventDefault();
window.location.hash = this.props.to;
};
render() {
return (
<a href={`#${this.props.to}`} onClick={this.handleClick}>
{this.props.children}
</a>
);
}
}
export { HashRouter, Route, Link };
手动实现简易 BrowserRouter
import React, { Component } from "react";
const RouterContext = React.createContext();
class BrowserRouter extends Component {
constructor(props) {
super(props);
this.state = {
location: {
pathname: window.location.pathname
}
};
}
componentDidMount() {
// 监听 popstate 事件(浏览器前进/后退)
window.addEventListener("popstate", () => {
this.setState({
location: {
pathname: window.location.pathname
}
});
});
}
render() {
const value = {
location: this.state.location,
history: {
push: (path) => {
window.history.pushState({}, "", path);
// 手动触发更新
this.setState({
location: { pathname: path }
});
},
replace: (path) => {
window.history.replaceState({}, "", path);
this.setState({
location: { pathname: path }
});
}
}
};
return (
<RouterContext.Provider value={value}>
{this.props.children}
</RouterContext.Provider>
);
}
}
// Link 组件(阻止默认行为,使用 history.push)
class Link extends Component {
static contextType = RouterContext;
handleClick = (e) => {
e.preventDefault();
this.context.history.push(this.props.to);
};
render() {
return (
<a href={this.props.to} onClick={this.handleClick}>
{this.props.children}
</a>
);
}
}
export { BrowserRouter, Link };
深入理解(原理剖析)
Hash 模式原理
┌─────────────────────────────────────────────────────────────┐
│ Hash 模式工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. URL 格式: http://example.com/#/path │
│ │
│ 2. hash 值改变不会发送 HTTP 请求到服务器 │
│ │
│ 3. 监听 hashchange 事件: │
│ window.addEventListener('hashchange', callback) │
│ │
│ 4. 根据新的 hash 值渲染对应组件 │
│ │
│ 5. 浏览器前进/后退同样触发 hashchange │
│ │
└─────────────────────────────────────────────────────────────┘
核心代码逻辑:
// HashRouter 核心实现
class HashRouter {
constructor() {
this.listeners = [];
// 监听 hash 变化
window.addEventListener('hashchange', () => {
this.notify();
});
}
// 获取当前路径(去掉 #)
get location() {
return window.location.hash.slice(1) || '/';
}
// 订阅变化
listen(callback) {
this.listeners.push(callback);
return () => {
this.listeners = this.listeners.filter(fn => fn !== callback);
};
}
// 通知所有订阅者
notify() {
this.listeners.forEach(fn => fn(this.location));
}
// 导航到指定路径
push(path) {
window.location.hash = path;
}
}
History 模式原理
┌─────────────────────────────────────────────────────────────┐
│ History 模式工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. URL 格式: http://example.com/path │
│ │
│ 2. 使用 History API: │
│ - history.pushState(state, title, url) │
│ - history.replaceState(state, title, url) │
│ │
│ 3. 监听 popstate 事件(前进/后退): │
│ window.addEventListener('popstate', callback) │
│ │
│ 4. 注意: pushState/replaceState 不会触发 popstate │
│ 需要手动触发组件更新 │
│ │
│ 5. 服务端需要配置 fallback 到 index.html │
│ │
└─────────────────────────────────────────────────────────────┘
核心代码逻辑:
// BrowserRouter 核心实现
class BrowserRouter {
constructor() {
this.listeners = [];
// 监听浏览器前进/后退
window.addEventListener('popstate', () => {
this.notify();
});
}
get location() {
return window.location.pathname;
}
listen(callback) {
this.listeners.push(callback);
return () => {
this.listeners = this.listeners.filter(fn => fn !== callback);
};
}
notify() {
this.listeners.forEach(fn => fn(this.location));
}
// 使用 pushState 导航
push(path) {
window.history.pushState({}, '', path);
// pushState 不会触发 popstate,需要手动通知
this.notify();
}
// 使用 replaceState 导航
replace(path) {
window.history.replaceState({}, '', path);
this.notify();
}
// 前进/后退
go(n) {
window.history.go(n);
}
back() {
this.go(-1);
}
forward() {
this.go(1);
}
}
两种模式对比
| 特性 | Hash 模式 | History 模式 |
|---|---|---|
| URL 美观度 | 有 #,不够美观 |
无 #,美观 |
| 兼容性 | IE8+ | IE10+ |
| 服务端配置 | 不需要 | 需要配置 fallback |
| SEO | 较差 | 较好 |
| 实现原理 | hashchange 事件 | History API |
| 刷新 404 | 不会出现 | 需要服务端处理 |
最佳实践
1. 根据场景选择模式
// 内部系统、后台管理 - 可以使用 HashRouter
import { HashRouter } from "react-router-dom";
// 面向用户的网站 - 推荐使用 BrowserRouter
import { BrowserRouter } from "react-router-dom";
2. 服务端配置(History 模式)
Nginx 配置:
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
Apache 配置:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
Node.js/Express:
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'build')));
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
3. 路由匹配原理
// 使用 path-to-regexp 进行路径匹配
const pathToRegexp = require('path-to-regexp');
// 编译路径为正则
const keys = [];
const regexp = pathToRegexp('/user/:id', keys);
// 匹配
const match = regexp.exec('/user/123');
// match = ['/user/123', '123']
// 提取参数
const params = {};
keys.forEach((key, index) => {
params[key.name] = match[index + 1];
});
// params = { id: '123' }
4. 路由守卫实现
import { Route, Redirect } from "react-router-dom";
// 权限路由守卫
function AuthRoute({ component: Component, requiresAuth, ...rest }) {
const isAuthenticated = checkAuth(); // 检查登录状态
return (
<Route
{...rest}
render={(props) => {
if (!requiresAuth) {
return <Component {...props} />;
}
return isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
);
}}
/>
);
}
面试要点
-
两种模式: Hash 模式和 History 模式
-
Hash 模式原理:
- 利用 URL 中的
#部分 - hash 变化不会发送请求到服务器
- 通过
hashchange事件监听变化
- 利用 URL 中的
-
History 模式原理:
- 使用 HTML5 History API (
pushState/replaceState) - 通过
popstate事件监听前进/后退 - 需要服务端配置支持
- 使用 HTML5 History API (
-
选择建议:
- 需要兼容旧浏览器 -> Hash 模式
- 需要更好的 SEO -> History 模式
- 内部系统 -> Hash 模式
- 面向用户的网站 -> History 模式
-
注意事项:
- History 模式刷新页面会 404,需要服务端配置
- Hash 模式的锚点功能会冲突
- Hash 模式不利于 SEO