(笔记)前端路由

664 阅读5分钟

1. 什么是前端路由

前端路由是通过url路径来匹配对应的代码块的这么一套机制

  1. 修改 url 后页面要更新
  2. 浏览器不能刷新
  3. 如何监听url变更

2. 为什么要使用前端路由?

传统网页(非单页应用)每次跳转都要重新加载整个页面,就像每次翻书都要重新印刷整本书一样浪费资源。而前端路由的优点是:

  • 页面切换更快(只更新变化的部分)
  • 用户体验更好(类似原生 App 的流畅感)
  • 更适合构建单页应用(SPA)

3. hash路由

3.1 识别特征

URL 中带 # 号,比如:

https://example.com/#/home
https://example.com/#/about

3.2 工作原理

  • 改变 # 后的内容不会触发页面刷新(这是浏览器的天然特性)
  • 通过监听 hashchange 事件感知 URL 变化
  • 根据不同的 hash 值显示对应内容

3.3 demo演示

<body>
    <!-- 导航链接:通过 # 的方式切换路由 -->
    <ul>
        <li>
            <!-- 点击后地址栏会变成 #/home -->
            <a href="#/home">首页</a>
        </li>
        <li>
            <!-- 点击后地址栏会变成 #/about -->
            <a href="#/about">关于</a>
        </li>
    </ul>

    <!-- 内容显示区域 -->
    <div id="app"></div>

    <script>
        // 路由配置表:定义每个路径对应的组件
        const routes = [
            {
                path: '/home',       // 路径
                component: () => {   // 对应的组件(返回HTML字符串的函数)
                    return '<h2>首页页面</h2>';
                }
            },
            {
                path: '/about',
                component: () => {
                    return '<h3>about页面</h3>';
                }
            }
        ]

        // 页面首次加载时执行:读取当前 hash 并显示对应内容
        window.addEventListener('DOMContentLoaded', () => {
            // location.hash 格式是 "#/home" 这种带 # 的
            routerView(location.hash)
        })

        // 监听 hash 变化:当点击链接或手动修改 hash 时触发
        window.addEventListener('hashchange', () => {
            routerView(location.hash)
        })

        // 获取内容容器元素
        const app = document.querySelector('#app');
        
        // 核心路由函数:根据 hash 显示对应内容
        function routerView(localHash) {
            // 在路由表中查找匹配当前 hash 的项
            const index = routes.findIndex((item) => {
                // 拼接成 "#/path" 的形式进行比较
                return '#' + item.path === localHash
            })

            // 如果找到匹配项(index不是-1),渲染对应组件
            if (index !== -1) {
                app.innerHTML = routes[index].component()
            } else {
                // 这里可以添加404处理(当前demo暂未实现)
                app.innerHTML = '<p>页面不存在</p>'
            }
        }
    </script>
</body>

关键流程说明:

  1. 用户点击链接 -> 修改地址栏 hash(例如变成 #/home
  2. 触发 hashchange 事件 -> 调用 routerView 函数
  3. 查找匹配路由 -> 在 routes 数组中找 path 对应的配置项
  4. 渲染组件 -> 执行对应组件的函数,将返回的HTML插入到页面

需要注意的小细节:

  1. location.hash 的值始终以 # 开头(比如 #/home
  2. findIndex 方法如果找不到会返回 -1(需要处理未匹配的情况)
  3. 直接修改地址栏的 hash 也会触发页面内容变化(比如手动输入#/about)

3.4 优缺点

  • ✅优点:兼容性好(连 IE8 都支持)
  • ❌ 缺点:URL 中有 # 号不够美观

4. history 路由

4.1 工作原理

首先要阻止 a 标签的默认行为,然后使用 history.pushState() 来改变 url 路径,它是不会引起页面的重新加载的,在 url 变更后,读取本地的 pathname 来判断要展示的组件

考虑到浏览器的前进后退按钮的存在,需要监听 popstate 事件,来判断要展示的对应的组件

4.2 demo演示

<body>
    <!-- 导航链接:通过普通路径的方式切换路由 -->
    <ul>
        <li>
            <!-- 点击后地址栏会变成 /home -->
            <a href="/home">首页</a>
        </li>
        <li>
            <!-- 点击后地址栏会变成 /about -->
            <a href="/about">关于</a>
        </li>
    </ul>

    <!-- 内容显示区域 -->
    <div id="app"></div>

    <script>
        // 路由配置表:定义每个路径对应的组件
        const routes = [
            {
                path: '/home',       // 路径
                component: () => {  // 对应的组件(返回HTML字符串的函数)
                    return '<h2>首页页面</h2>';
                }
            },
            {
                path: '/about',
                component: () => {
                    return '<h3>about页面</h3>';
                }
            }
        ]

        // 监听浏览器前进/后退按钮:当用户点击前进或后退时触发
        window.addEventListener('popstate', function () {
            // 调用 routerView 函数,根据当前路径更新页面内容
            routerView()
        })

        // 获取所有 a 标签
        let linkList = document.querySelectorAll('a')
        // 为每个 a 标签绑定点击事件
        linkList.forEach(link => {
            link.addEventListener('click', function (e) {
                // 阻止 a 标签的默认跳转行为(避免页面刷新)
                e.preventDefault()

                // 使用 history.pushState 修改浏览器地址栏的 URL
                // 参数说明:
                // 1. null:不需要传递额外数据
                // 2. '':不需要修改标题
                // 3. this.getAttribute('href'):获取 a 标签的 href 属性值(如 /home)
                history.pushState(null, '', this.getAttribute('href'))

                // 调用 routerView 函数,更新页面内容
                routerView()
            })
        })

        // 获取内容容器元素
        const app = document.getElementById('app')

        // 核心路由函数:根据当前路径显示对应内容
        function routerView() {
            // 在路由表中查找匹配当前路径的项
            const index = routes.findIndex(item => {
                // location.pathname 是当前 URL 的路径部分(如 /home)
                return item.path === location.pathname
            })

            // 如果找到匹配项(index不是-1),渲染对应组件
            if (index !== -1) {
                app.innerHTML = routes[index].component()
            } else {
                // 这里可以添加404处理(当前demo暂未实现)
                app.innerHTML = '<p>页面不存在</p>'
            }
        }
    </script>
</body>

关键流程说明:

  1. 用户点击链接 -> 触发点击事件
  2. 阻止默认跳转 -> 使用 e.preventDefault() 防止页面刷新
  3. 修改 URL -> 使用 history.pushState 修改地址栏路径
  4. 更新页面内容 -> 调用 routerView 函数,根据当前路径渲染对应组件

需要注意的小细节:

  1. history.pushState 的作用

    • 修改浏览器地址栏的 URL
    • 不会触发页面刷新
    • 会添加一条历史记录
  2. popstate 事件的作用

    • 监听浏览器的前进/后退操作
    • 当用户点击前进或后退时,更新页面内容
  3. location.pathname 的值

    • 是当前 URL 的路径部分(如 /home 或 /about
    • 不包含域名和查询参数
  4. 服务器配置

    • 如果用户手动刷新页面,浏览器会向服务器请求当前路径(如 /home
    • 需要服务器配置,将所有路径返回 index.html,否则会 404

4.3 优缺点

  • ✅ 优点:URL 更简洁美观

  • ❌ 缺点:

    • 兼容性稍差(不支持 IE10 以下)
    • 需要服务器配置(所有路径返回 index.html)