HashRouter 和 BrowserRouter 区别、底层原理、部署差异

HashRouterBrowserRouter 是 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 则是更省心的方案。

相关推荐
柯克七七1 小时前
我把祖传项目的构建时间砍了90%,领导以为我只是在"优化了一下",结果隔壁组的CI都崩了来问我配置
前端·webpack
风骏时光牛马1 小时前
JSP页面直接输出实体对象空属性引发页面500报错实战案例
前端
IT_陈寒2 小时前
Python里这个赋值坑,连老司机都能翻车
前端·人工智能·后端
Hyyy3 小时前
什么是bun?和pnpm有什么区别
前端·面试·bun
IT_陈寒16 小时前
Vue这个坑我跳了两次,原来问题出在这
前端·人工智能·后端
kyriewen16 小时前
我用 50 行代码重写了 React Router 核心,终于搞懂了前端路由原理
前端·javascript·react.js
WebInfra17 小时前
Rspack 2.1 发布:React Compiler 提速 10 倍!
前端
李明卫杭州17 小时前
CSS 媒体查询详解:一文掌握响应式设计的核心技术
前端
悟空瞎说17 小时前
NestJS 12 预览版重磅来袭:全面原生 ESM 正式落地
nestjs