前端路由:理解 hash 路由和 history 路由原理

什么是路由?路由就是用来描述服务器上的资源的路径。本文我们来理解前端路由。

一、为什么要理解前端路由?

在传统多页面应用中,每次切换页面都要重新请求 HTML,这样如果用户网络波动较大,就会导致用户切换页面时浏览器上一片空白,需要等待几秒才能渲染出来。而单页应用不刷新页面,而是只切换组件。

单页应用怎么实现呢?我们这里用原生 JS 简单的实现一下:

html 复制代码
<body>
    <header>
        <nav>首页</nav>
        <nav>沸点</nav>
    </header>
    <div id="root"></div>
</body>
js 复制代码
const content = [
        '<h2 class="home">首页</h2>',
        '<h3 class="hot">沸点</h3>',
        ]
let navs = document.getElementsByTagName('nav');
        Array.from(navs).forEach((nav, index) => nav.addEventListener('click', () => {
            document.getElementById('root').innerHTML = content[index];
        }))

这样就实现了一个点击导航能够手动切换 root 中的内容的简单单页应用,但是并没有改变 URL,只能实现"组件切换"。

前端路由的核心作用是:

  • 改变浏览器 URL
  • 页面不刷新
  • 根据 URL 找到对应组件并渲染

也就是说,前端路由是构建浏览器 URL 地址和组件之间的映射关系。

二、前端路由的核心流程

  1. 修改浏览器 URL,但不刷新页面
  2. 监听 URL 的变化
  3. 根据 URL 从映射表中找到对应的组件并渲染

我们有两种方法来实现:hash 路由和 history 路由

三、hash 原理

  1. hash 是什么?

例如:

http://localhost:3000/#/home

其中:#/home 就是 hash。 也就是说,浏览器会将 URL 地址后面接"#" 的这串值会被认为是一串 hash 值。

  1. hash 的特点
  • URL 中 # 后面的内容变化不会导致页面刷新
  • 可以通过location.hash 获取当前的 hash
  • 可以监听hashchange 事件
  1. hash 路由实现步骤
  • 定义路由映射表
  • 点击导航改变 hash
  • 监听 hashchange
  • 根据 location.hash 找到对应的组件
  • 渲染到页面
  1. 映射表
js 复制代码
const routes = [
    {
        path: '/home',
        component: () => {
            return '<h2>首页页面</h2>'
        }
    },
    {
        path: '/hot',
        component: () => {
            return '<h3>沸点页面</h3>'
        }
    }
]
  1. 监听 hashchange
js 复制代码
// 知道url变更了
window.addEventListener('hashchange', () => {
    // 在routes中找到对应的path,并渲染对应的组件
    renderView(location.hash)
})
  1. 渲染函数
js 复制代码
function renderView(hashVal) {
     hashVal = hashVal.replace('#', '')
     let route = routes.find(item => item.path === hashVal)
     if(route) {
         document.getElementById('root').innerHTML = route.component()
     }
 }
  1. 处理首次加载
js 复制代码
// 初次刷新页面
window.addEventListener('DOMContentLoaded', () => {
    renderView(location.hash)
})
  1. hash 路由小结 优点:
  • 实现简单
  • 兼容性好
  • 切换 URL 不会刷新页面

缺点:

  • URL 中有#
  • 不够美观
  • 不利于一些 SED 场景

四、history 路由原理

  1. history 是什么?

history 是浏览器提供的一个对象,用来管理浏览器的历史记录。

  1. 提供的方法
  • pushState() 向浏览器的历史记录栈中添加一条记录
  • popState() 从浏览器的记录中出栈一条记录
  • replaceState() 替换浏览器历史记录中的栈顶记录

pushState() 可以修改 URL 且不带来页面刷新,监听 popState() 来关联浏览器的前进后退事件

五、手写 history 路由

  1. 定义路由表
js 复制代码
const routes = [
     {
         path: '/home',
         component: () => {
             return '<h2>history 首页页面</h2>'
         }
     },
     {
         path: '/hot',
         component: () => {
             return '<h3>history 沸点页面</h3>'
         }
     }
 ]
  1. 阻止默认跳转
js 复制代码
e.preventDefault()
  1. 使用 pushState 修改 URL
js 复制代码
history.pushState(null, '', el.getAttribute('href')) // 进入浏览器的缓存栈

这一步可以做到:

  • URL 改变
  • 页面不刷新
  • 浏览器历史记录增加一条
  1. 手动渲染页面
js 复制代码
renderView(location.pathname)
  1. 监听浏览器前进后退
js 复制代码
window.addEventListener('popstate', () => {
    renderView(location.pathname)
})
  1. 渲染函数
js 复制代码
function onLoad() {
    // 路径变更,渲染对应组件
    let linkList = document.querySelectorAll('a[href]')
    linkList.forEach(el => {
        el.addEventListener('click', (e) => {
            e.preventDefault() // 阻止 a 标签默认跳转行为
            history.pushState(null, '', el.getAttribute('href')) // 进入浏览器的缓存栈

            renderView(location.pathname)
        })
    })
}

function renderView(historyPath) {
    routes.forEach(item => {
        if (item.path === historyPath) {
            document.getElementById('root').innerHTML = item.component()
        }
    })
}

六、总结

前端路由的本质,是在不刷新页面的情况下,根据浏览器 URL 的变化来切换页面内容。它通过维护一套路由映射表,将不同的路径和不同的组件对应起来。当 URL 发生变化时,前端会监听这个变化,找到对应的组件并渲染到页面中。

常见的前端路由实现方式有两种: hash 模式和 history 模式。 hash 模式通过监听 hashchange 事件实现路由切换,特点是实现简单、兼容性好,但 URL 中会带有 # 。 history 模式通过 history.pushState 修改 URL,并配合 popstate 监听浏览器前进后退,URL 更加美观,但项目上线时需要服务端配置兜底,否则刷新页面可能会出现 404。

相关推荐
胡萝卜术1 小时前
从暴力到Z字形消元:力扣240「搜索二维矩阵II」的降维打击之路
前端·javascript·面试
比老马还六1 小时前
Bipes-Blockly项目二次开发/Coze智能体(十)
前端·嵌入式
1 小时前
Vue 3 组件封装与使用:保姆级教程
前端
星辰1 小时前
深入浅出 Android AOA 协议:通信流程与设备切换附着机制解析
前端
恋猫de小郭2 小时前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
敲代码的彭于晏2 小时前
Bean 生命周期完全图解:前端同学也能看懂的 Spring 核心机制
java·前端·后端
IT_陈寒2 小时前
Redis内存飙升的锅,原来是我没搞懂这个过期策略
前端·人工智能·后端
云浪2 小时前
前端二进制数组完全指南:ArrayBuffer、TypedArray、DataView 一次讲透
前端·javascript
张风捷特烈2 小时前
Flutter 类库大揭秘#02 | path_provider 各平台实现
前端·flutter