从history技术原理出发,探讨前端路由的实现过程

441 阅读5分钟

前言

上一篇文章我们是从hash技术原理出发,探讨前端路由的实现过程。所以这篇文章我们要讲另一种技术原理也就是history来实现前端路由,在前端路由实现中,History API 是一种较新的、更加优雅的方案,它能够修改浏览器的 URL 而不触发页面刷新,同时提供丰富的历史记录管理功能。相比于传统的 Hash 路由,History API 的 URL 更加清晰和符合 RESTful 设计规范,并且有助于改善用户体验。我们在本文将深入解析 History API 的核心方法及其在前端路由中的应用。

正文

History API 核心概念

History API 允许开发者对浏览器的历史记录栈进行操作,包括添加、修改和替换历史记录条目。核心方法和事件包括:

  1. pushState 方法

    • 用于向历史记录栈中添加新的记录。

    • 调用 pushState 后,浏览器地址栏会更新为指定的 URL,但不会触发页面刷新。

    • 语法

      javascript
      复制代码
      history.pushState(state, title, url);
      
      • state:任意 JavaScript 对象,表示与新历史记录关联的状态信息。
      • title:通常传入空字符串,因为大多数浏览器目前不使用该参数。
      • url:新的 URL 地址。
  2. replaceState 方法

    • 替换当前历史记录条目,而不是添加新条目。

    • 使用场景:如在 SPA 中跳转到相同页面但需更新 URL 参数。

    • 语法

      javascript
      复制代码
      history.replaceState(state, title, url);
      
  3. popstate 事件

    • 当用户点击浏览器的前进或后退按钮时,会触发 popstate 事件。
    • 注意:调用 pushStatereplaceState 不会触发该事件,只有浏览器导航操作会触发。

代码示例

接下来我们直接用代码来讲解:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul>
        <li><a href="/home">首页</a></li>
        <li><a href="/about">关于</a></li>
    </ul>

    <div id="routeView">
        <!-- 放一个代码片段 -->

    </div>



    <script>
        const routes = [
            {
                path: '/home',
                component: '<h2>首页页面内容</h2>',
            },
            {
                path: '/about',
                component: '<h3>about page</h3>'
            }
        ]

        const routeView = document.getElementById('routeView')

        window.addEventListener('DOMContentLoaded', onLoad)
        window.addEventListener('popstate', onPopState)

        function onLoad() {
            const links = document.querySelectorAll('li a')
            links.forEach(a => {
                a.addEventListener('click', (e) => {
                    e.preventDefault() // 阻止了a标签的默认跳转行为
                    // 添加一种可以修改url又不造成页面刷新
                    history.pushState(null, '', a.getAttribute('href'))
                    // 映射对应的dom
                    onPopState()
                })
            })
        }


        // const links = document.querySelectorAll('li a')
        // links.forEach(a => {
        //     a.addEventListener('click', (e) => {
        //         e.preventDefault() // 阻止了a标签的默认跳转行为
        //         // 添加一种可以修改url又不造成页面刷新
        //         history.pushState(null, '', a.getAttribute('href'))
        //         // 映射对应的dom
        //         onPopState()
        //     })
        // })


        function onPopState() {
            console.log(location.pathname);
            routes.forEach(item => {
                if (item.path === location.pathname) {
                    routeView.innerHTML = item.component
                }
            })
        }

    </script>
</body>

</html>

代码讲解

代码解析

  1. HTML 部分

    <ul>
        <li><a href="/home">首页</a></li>
        <li><a href="/about">关于</a></li>
    </ul>
    
    <div id="routeView">
        <!-- 放一个代码片段 -->
    </div>
    
    • 导航链接<a> 标签作为路由链接,点击时会修改浏览器 URL 中的路径部分(例如 /home/about)。
    • routeView 容器:该容器用于动态渲染与当前 URL 路径对应的页面内容。
  2. JavaScript 部分

    • routes 配置:定义了一个路由配置对象数组,映射了路径和对应的组件(这里用 HTML 字符串表示)。

      const routes = [
          {
              path: '/home',
              component: '<h2>首页页面内容</h2>',
          },
          {
              path: '/about',
              component: '<h3>about page</h3>'
          }
      ]
      
    • 事件监听

      • DOMContentLoaded 事件:当文档加载完成后,执行 onLoad 函数,初始化链接的事件处理。
      • popstate 事件:监听浏览器的前进和后退操作,当用户改变浏览器历史记录时触发,执行 onPopState 来更新页面内容。
  3. onLoad 函数

    function onLoad() {
        const links = document.querySelectorAll('li a');
        links.forEach(a => {
            a.addEventListener('click', (e) => {
                e.preventDefault(); // 阻止a标签的默认跳转行为
                history.pushState(null, '', a.getAttribute('href')); // 修改URL,但不刷新页面
                onPopState(); // 更新页面内容
            });
        });
    }
    
    • 阻止默认跳转:通过 e.preventDefault() 阻止 <a> 标签默认的跳转行为,避免触发页面刷新。
    • 修改 URL:使用 history.pushState() 修改 URL 中的路径部分。此操作不会导致页面刷新,只是更新了浏览器的历史记录栈。
    • 渲染视图:调用 onPopState(),通过 URL 的变化来渲染相应的页面内容。
  4. onPopState 函数

    function onPopState() {
        console.log(location.pathname);
        routes.forEach(item => {
            if (item.path === location.pathname) {
                routeView.innerHTML = item.component;
            }
        });
    }
    
    • location.pathname:通过 location.pathname 获取当前的 URL 路径。
    • 路由匹配:遍历 routes 数组,检查当前路径是否与路由配置中的 path 匹配。
    • 渲染对应组件:如果路径匹配,则将对应的 component 内容插入到 routeView 容器中,从而更新页面内容。

小结

  • URL 路径与页面内容的映射: 通过将 URL 路径和页面内容进行映射(routes 数组),实现了基于路径的页面切换。

  • 使用 history.pushState 修改 URLpushState 修改了浏览器的地址栏,而不引起页面的刷新。这是实现前端路由的核心机制,通过 pushState 可以动态更新 URL。

  • popstate 事件监听: 当用户通过浏览器的前进和后退按钮导航时,popstate 事件会被触发,调用 onPopState 更新页面内容。

  • 避免页面刷新: 与传统的多页面应用不同,单页面应用通过修改 URL 来模拟页面切换,但不会重新加载页面。这样做大大提高了应用性能,减少了不必要的页面刷新。

总结

History 路由与 Hash 路由简要对比总结

  1. Hash 路由

    • 原理:通过修改 URL 中的 # 符号后面的部分来实现路径切换,不会引起页面刷新。
    • 优点:实现简单,不需要服务器配置,兼容性强。
    • 缺点:URL 中包含 #,不符合 RESTful 设计,SEO 不友好,功能上较为简单。
  2. History 路由

    • 原理:通过 HTML5 History APIpushStatepopState 方法修改浏览器历史记录和 URL,避免页面刷新。
    • 优点:URL 清晰美观,符合 RESTful 设计,支持复杂路由和动态参数,SEO 友好。
    • 缺点:需要服务器配置,支持较差的老旧浏览器,较 Hash 路由实现稍复杂。

总的来说:

  • Hash 路由:适合简单应用,兼容性好,使用方便,但 URL 不美观且 SEO 性能差。
  • History 路由:适合需要清晰 URL 和 SEO 优化的应用,支持复杂功能,但需要服务器配置和兼容性管理。

无论你是前端新手还是资深开发者,掌握好 History 路由Hash 路由 的区别,将让你在开发 SPA 时如虎添翼!当然,选择哪个方案也要看你的项目需求和兼容性要求。希望这篇文章能帮助你在路由的世界里迷失不再——别忘了给我点个赞,关注一下,带着你的小伙伴一起来探讨更多前端小技巧!🚀

祝你在代码的海洋中畅游,不迷路!👨‍💻👩‍💻