HashRouter 和 BrowserRouter 是 React 中最常用的两种路由模式,它们最大的区别就是 URL 的表现形式、浏览器工作原理以及服务器部署方式。
如果你面试 React,几乎都会问到这个问题。
一、先看区别
假设有一个用户页面。
HashRouter
bash
http://localhost:3000/#/user
URL 中有
bash
#
例如
bash
http://localhost:3000/#/
http://localhost:3000/#/home
http://localhost:3000/#/login
BrowserRouter
没有 #
bash
http://localhost:3000/user
例如
bash
http://localhost:3000/
http://localhost:3000/home
http://localhost:3000/login
更加美观。
对比
| 对比项 | HashRouter | BrowserRouter |
|---|---|---|
| URL | /#/home | /home |
| 是否有 # | 有 | 无 |
| SEO | 差 | 好 |
| 刷新页面 | 不会404 | 容易404 |
| 部署 | 简单 | 需要服务器支持 |
| 原理 | hashchange | History API |
| 推荐 | 后台管理 | 官网、商城、门户 |
二、HashRouter 底层原理
HashRouter 利用了浏览器 URL 的
bash
#
锚点(hash)。
例如:
bash
https://abc.com/#/home
浏览器真正访问服务器的是:
arduino
https://abc.com/
后面的
shell
#/home
不会发送给服务器。
浏览器自己保存。
例如:
bash
https://abc.com/#/user?id=1
服务器收到的是
sql
GET /
而不是
sql
GET /user
所以服务器永远不会404。
hash 的特点
修改 hash
ini
location.hash = "/home"
浏览器不会刷新页面。
只会触发
javascript
window.onhashchange = function () {
console.log(location.hash);
}
或者
javascript
window.addEventListener("hashchange", () => {
console.log(location.hash);
});
例如
ini
location.hash = "/home"
URL
arduino
localhost:3000/#/home
事件触发
hashchange
React Router 就监听这个事件。
流程:
bash
修改hash
│
▼
hashchange
│
▼
React Router
│
▼
重新匹配Route
│
▼
重新渲染组件
三、BrowserRouter 底层原理
BrowserRouter 使用浏览器提供的
History API
主要就是三个API:
scss
history.pushState()
history.replaceState()
window.onpopstate
而不是 hash。
例如
less
history.pushState({}, "", "/home");
URL
arduino
localhost:3000/home
页面不会刷新。
React Router 就重新渲染。
返回按钮
浏览器点击
←
触发
javascript
window.onpopstate
React Router 接收到以后
重新匹配
arduino
/home
渲染页面。
流程
bash
pushState
│
▼
history
│
▼
onpopstate
│
▼
React Router
│
▼
重新渲染
四、为什么 BrowserRouter 会404?
这是最经典的面试题。
假设:
React
arduino
localhost:3000/home
刷新一下。
浏览器发送:
arduino
GET /home
服务器收到以后:
arduino
找/home目录
但是服务器里面只有
diff
index.html
没有
arduino
/home
于是
404
React 根本没有机会运行。
HashRouter 为什么不会?
因为服务器收到的是
sql
GET /
React 启动以后
读取
python
location.hash
得到
shell
#/home
所以正常渲染。
五、BrowserRouter 为什么需要服务器配置?
例如
React
arduino
/home
刷新
浏览器:
arduino
GET /home
服务器应该这样配置:
markdown
任何路径
│
▼
都返回 index.html
React 接管路由。
例如
bash
GET /login
返回
diff
index.html
React
ini
<Route path="/login">
渲染 Login。
所以 SPA 都需要
Fallback
六、不同服务器如何配置
Nginx
最经典:
bash
location / {
try_files $uri $uri/ /index.html;
}
意思:
先找真实文件
arduino
/home
没有的话
diff
index.html
React 接管。
Apache
perl
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^ index.html [L]
Express
csharp
app.use(express.static("build"));
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "build/index.html"));
});
Nest
csharp
app.useStaticAssets(join(__dirname, "public"));
app.get("*", (req, res) => {
res.sendFile(join(__dirname, "public/index.html"));
});
Node
erlang
if (!fileExists(req.url)) {
res.sendFile("index.html");
}
七、React Router 内部工作流程
假设:
点击
ini
<Link to="/home">
流程:
bash
点击Link
│
▼
preventDefault
│
▼
pushState
│
▼
更新history
│
▼
通知Router
│
▼
重新匹配Route
│
▼
渲染Home组件
整个过程中
没有刷新页面。
HashRouter
ini
点击Link
│
▼
location.hash="/home"
│
▼
hashchange
│
▼
Router重新匹配
│
▼
Home组件
八、源码思想(简化版)
HashRouter
javascript
window.addEventListener("hashchange", () => {
render(location.hash);
});
BrowserRouter
javascript
window.addEventListener("popstate", () => {
render(location.pathname);
});
导航:
lua
function push(path) {
history.pushState({}, "", path);
render(path);
}
九、History API 详解
pushState
新增一条历史记录
less
history.pushState({}, "", "/user");
历史记录:
bash
/
↓
/home
↓
/user
点击返回:
arduino
/home
replaceState
替换当前历史记录
less
history.replaceState({}, "", "/login");
历史记录:
bash
/
↓
/login
不会新增记录。
popstate
用户:
← 返回
触发:
javascript
window.addEventListener("popstate", () => {
console.log(location.pathname);
});
React Router 就是在这里更新页面。
十、实际开发如何选择?
- 后台管理系统(如 OA、CRM、数据平台) :优先使用
HashRouter,部署简单,不依赖服务器配置,在静态托管环境中也能正常工作。 - 企业官网、营销站点、商城、博客等需要更友好 URL 或 SEO 的应用 :优先使用
BrowserRouter,但需要配置服务器将未知路径统一回退到index.html。
如果你的项目部署在支持自定义路由回退的服务器(如 Nginx、Express、Nest 等),BrowserRouter 通常是更现代、更推荐的选择;如果部署环境无法修改服务器配置(例如某些静态文件服务器或受限环境),HashRouter 则是更省心的方案。