动手实现一个简易前端路由:理解 React Router 的本质

手动实现一个简易前端路由:理解React Router的本质

一、从传统页面切换到单页应用(SPA)

在传统的网页开发中,每次页面的切换都需要向服务器发起一次请求,获取新的 HTML 页面内容,并进行重新渲染。这种方式虽然简单直接,但存在明显的用户体验问题:页面加载时会出现白屏、卡顿,甚至需要等待较长时间才能看到新页面的内容。

为了解决这些问题,单页应用(Single Page Application, SPA) 应运而生。SPA 的核心思想是:只加载一个 HTML 页面,通过 JavaScript 动态地更新页面内容,实现多个"页面"的效果,从而避免了频繁的页面刷新和白屏现象。

在 React 生态中,react-router-dom 是目前最流行的路由解决方案之一。它帮助我们优雅地管理 URL 和组件之间的映射关系,实现 SPA 中的页面切换体验。

二、SPA实现原理

在 SPA 中,页面切换的核心机制是 监听 URL 的变化,而不是每次都向服务器请求新的 HTML 文件。

主要有两种技术实现URL变化不刷新页面:

  1. Hash 模式(锚点模式)

    Hash(#)最初被设计为页面内锚点定位功能,功能如下

    • 文档内导航

      点击链接直接滚动到页面指定位置,不触发页面重新加载

      html 复制代码
      <a href="#section1">跳转到第一节</a>
      ...
      <h2 id="section1">第一节</h2>

    Hash模式利用了URL 中的 hash(# 后面的部分)来实现无刷新页面导航因为浏览器对 hash 的改变不会向服务器发送请求)。

    # 之前的部分是常规 URL,# 之后的部分是前端路由控制的 hash 部分。可以通过window.location.hash来获取

    bash 复制代码
    http://example.com/#/home

    当hash的值发生改变时不会触发页面刷新,会触发 hashchange 事件。我们可以在事件的回调函数中进行页面的局部切换

    hash实现SPA示例:

    html 复制代码
        <nav>
            <ul>
                <li><a href="#home">首页</a></li>
                 <li><a href="#content">内容页</a></li>
                <li><a href="#about">About</a></li>
            </ul>
        </nav>
        <div id="container">  
            <h1>Contact</h1>
            <p>这是Contact</p>
        </div>
        <script>
            const container = document.getElementById('container')
            window.addEventListener('hashchange',()=>{
                console.log('hashChange')
                const hash = window.location.hash
                switch (hash) {
                    case '#home':
                        container.innerHTML = '<h1>Home</h1><p>这是Home</p>'
                        break;
                    case '#content':
                        container.innerHTML = '<h1>内容页</h1><p>这是内容页</p>'
                        break;
                    case '#about':
                        container.innerHTML = '<h1>About</h1><p>这是About</p>'
                        break;
                    default:
                        break;
                }
            })
        </script>
  2. History 模式(HTML5 History API)

    HTML5 History API 是现代前端路由的核心技术,它允许开发者操作浏览器的会话历史记录(URL),而不会导致页面刷新 。React Router 默认使用的是 History 模式,但也支持 Hash 模式。

    特点: URL相较于hash更加美观,需要后端配合处理 404 路由回退到入口文件(如 index.html),现代浏览器广泛支持

    window.history的几个关键方法:

    方法 说明
    history.pushState(state, title, url) 添加新历史记录,URL 改变但页面不刷新
    history.replaceState(state, title, url) 替换当前历史记录,不新增条目
    history.go(n) 前进/后退 n 步(history.go(-1) 相当于 history.back()
    history.back() 后退一页
    history.forward() 前进一页

    pushStatereplaceState 详解

    • state :一个 JavaScript 对象,与新的历史记录关联(可用于 popstate 事件恢复状态)
    • title (大多数浏览器忽略,通常传 ""null
    • url (新 URL,必须是同源的,否则报错)

    示例:

    使用 history.pushState()history.replaceState() 方法来修改 URL。

    js 复制代码
    // 添加新历史记录,URL 变为 /about,但页面不刷新
    history.pushState({ page: "about" }, "", "/about");
    
    // 替换当前历史记录,不会产生新的历史条目
    history.replaceState({ page: "home" }, "", "/");

    使用history来实现SPA:

    html 复制代码
        <nav id="main-nav">
            <ul>
                <li><a href="/" data-route>首页</a></li>
                <li><a href="/content" data-route>内容页</a></li>
                <li><a href="/about" data-route>About</a></li>
            </ul>
        </nav>
        <div id="container">
            <h1>Home</h1>
            <p>这是首页</p>
        </div>
        <script>
            const nav = document.getElementById('main-nav')
            const container = document.getElementById('container')
    
            // 导航点击事件监听(事件委托模式)
            nav.addEventListener('click',(e)=>{
                e.preventDefault()
                // 通过data-route属性过滤有效路由链接
                let link = e.target.closest('[data-route]')
                if(!link) return 
                
                // 获取href路径并执行导航
                let path =link.getAttribute('href')
    
                navigateTo(path)
            })
    
            // 导航
            function navigateTo(path){
                // 使用History API修改地址栏(无刷新跳转)
                window.history.pushState({path},'',path)
                updateContent(path)
            }
    
            // 局部更新
            function updateContent(path){
                // 路由-内容映射表
                const content = {
                    '/':'<h1>Home</h1><p>这是首页</p>',
                    '/content':'<h1>Content</h1><p>这是内容页</p>',
                    '/about':'<h1>About</h1><p>这是About页</p>',
                }
                container.innerHTML = content[path] || '<h1>404</h1><p>页面未找到</p>';
            }
            
            // 浏览器前进/后退事件处理
            window.addEventListener('popstate',(e)=>{
                const path = e.state?.path || window.location.pathname
                updateContent(path)
            })
          </script>

三、React Router 如何工作?

React Router 本质上是一个基于 URL 匹配的组件系统。它的核心逻辑包括以下几个部分:

Router 组件:路由根容器

  • 所有路由相关的组件都必须包裹在 <Router> 中。
  • 在 v6+ 中,通常使用 <BrowserRouter>(基于 HTML5 History API)或 <HashRouter>(基于 hash 模式)作为顶层容器。
jsx 复制代码
import { BrowserRouter as Router } from 'react-router-dom';

function App() {
  return (
    <Router>
      {/* 子路由配置 */}
    </Router>
  );
}

Routes 组件:路由匹配出口

  • <Routes> 是一个路由匹配器,它会根据当前 URL 匹配第一个符合条件的 <Route>,并渲染对应的组件。类似于 switch-case 的结构。
jsx 复制代码
<Router>
	<Routes>
	  <Route path="/" element={<Home />} />
	  <Route path="/about" element={<About />} />
	</Routes>
</Router>

Route 组件:定义路由规则

  • <Route> 定义了 URL 路径(path)与组件(element)之间的映射关系。当 URL 匹配某个 <Route> 的 path 时,该组件就会被渲染。
jsx 复制代码
<Routes>
   <Route path="/about" element={<About />} />
</Routes>

Link 组件:替代原生 a 标签

原生的 <a> 标签会在点击时触发默认行为:发送 HTTP 请求,导致页面刷新。这显然违背了 SPA 的设计初衷。

因此,React Router 提供了 <Link> 组件来拦截点击事件,并通过 history.pushState()history.replaceState() 修改 URL,同时阻止默认行为,这样就可以在不刷新页面的前提下完成 URL 的变更和组件的切换。

jsx 复制代码
<Link to="/about">关于我们</Link>
jsx 复制代码
const handleClick = (e) => {
  e.preventDefault();
  navigate(to); // 内部调用 history.pushState
};

四、React路由切换背后的机制

  1. URL 变化监听

    React Router 内部监听 URL 的变化,无论是通过用户点击链接、浏览器前进/后退按钮,还是调用 navigate() 函数等方式改变 URL。

    • 对于 History 模式,它使用 popstate 事件监听浏览器的前进/后退操作。
    • 对于 Hash 模式,则监听 hashchange 事件。
    js 复制代码
    window.addEventListener('popstate', () => {
      console.log('URL changed via history.back or forward');
    });
  2. 路径匹配与组件渲染

    当 URL 发生变化后,React Router 会遍历所有的 <Route> 配置,找到匹配当前 URL 的组件,并将其渲染到 <Routes> 所在的位置。

    • 匹配过程是自上而下的,一旦找到第一个匹配项就停止(除非设置了 indexend 属性)。
    • 支持动态路由参数(如 /user/:id),方便构建 RESTful 风格的 URL。
  3. 局部更新而非整页刷新

    由于整个页面只有一个 HTML 文件,所有组件的切换都是通过 React 的虚拟 DOM Diffing 算法实现的,因此可以做到局部更新,极大提升了性能和用户体验。

相关推荐
hard_coding_wang14 分钟前
使用layui的前端框架过程中,无法加载css和js怎么办?
javascript·前端框架·layui
香蕉可乐荷包蛋22 分钟前
vue3中ref和reactive的使用、优化
前端·javascript·vue.js
勤奋的知更鸟30 分钟前
JavaScript 性能优化实战:深入性能瓶颈,精炼优化技巧与最佳实践
开发语言·javascript·性能优化
耶啵奶膘34 分钟前
css——width: fit-content 宽度、自适应
前端·css
OEC小胖胖36 分钟前
前端框架状态管理对比:Redux、MobX、Vuex 等的优劣与选择
前端·前端框架·web
字节架构前端1 小时前
k8s场景下的指标监控体系构建——Prometheus 简介
前端·架构
奕羽晨2 小时前
关于CSS的一些读书笔记
前端·css
Poetry2372 小时前
大屏数据可视化适配方案
前端
遂心_2 小时前
用React Hooks + Stylus打造文艺范的Todo应用
前端·javascript·react.js