路由hash模式和history模式的实现

560 阅读5分钟

1. 前端router是什么

在传统的html编程中,如果想要实现页面能够展示不同页面那么我们就得写很多的html页面来根据url进行展示,但是在vue项目中是一个单页应用的状态,在vue项目中只有一个index.html,所有的页面或者叫组件都是在这个html中展示,而vue中的router就可以实现根据不同url展示对应的组件。

router: 通过url路径来匹配对应的代码块的这么一套机制

router特点:

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

2. hash路由实现

const router = createRouter({
  history: createWebHistory(),
  routes: routes
})

当我们在vue项目中配置路由的时候,里面有一个属性叫history,这个属性有两个值其中的createWebHashHistory() 就是我们常说的hash模式,在这个模式中我们的url路径中会多加一个#除此之外与createWebHistory() 并无区别。

在简单回忆了一下router的hash是什么之后,接下来我们来根据router特点和hash模式来自己实现一下router中的hash模式,首先我们需要了解一个小知识点:

#/home 在浏览器的 url 中出现 # 号,#后面的内容会被看作是一个 hash 值,hash 值的变化不会引起页面的重新加载

在我们了解了这一个特点之后,我们就已经完成了router中的修改url之后浏览器不刷新这一特点,接下来我们只需要监听url变更,并且在修改url后页面刷新就行,接下来先给大家展示一下基础的html代码:

<!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="app"></div>

  <script>
    // 路由
    const routes = [
      {
        path: '/home',
        component: () => {
          return '<h2>首页页面</h2>'
        }
      },
      {
        path: '/about',
        component: () => {
          return '<h3>about页面</h3>'
        }
      },
    ]
  </script>
</body>
</html>

如果我们想要实现router剩下的两个特点,那么就需要对页面中url的变化进行监听,当页面中的url变化的时候,在页面执行对应的routes[x].component()就可以在页面展示对应的组件。这个功能我们用一个函数routerView(localHash)来实现加载对应路径组件这个功能:

const app = document.getElementById('app')
function routerView(localHash) {
  // 去 routes 数组中查找 localHash 值在哪一项中
  // 如果找到了,就将这一项中的component执行结果放到 app 容器中
  const index = routes.findIndex((item) => {
    return '#' + item.path === localHash
  })
  app.innerHTML = routes[index].component()
}

要想实现上述监听url功能,我们就得用官方为我们提供的'hashchange'事件,这个事件可以监听页面中hash值的变化,我们在url中用了#将后面的值变成了hash值,而不同的页面就会展示不同的url就会让hash值进行改变,这样就能通过这个事件来加载对应的组件。

// 浏览器自带的一个监听hash变化事件
window.addEventListener('hashchange', () => {
  // console.log(location.hash);// 获取到url变化
  routerView(location.hash)
})

同时我们需要在页面完成加载时,就展示对应的组件,完成这个功能才算实现了hash路由,这个功能我们通过官方提供的'DOMContentLoaded'事件来监听页面加载完成,接下来我们来看完整代码:

<!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="app"></div>

  <script>
    const routes = [
      {
        path: '/home',
        component: () => {
          return '<h2>首页页面</h2>'
        }
      },
      {
        path: '/about',
        component: () => {
          return '<h3>about页面</h3>'
        }
      },
    ]
    const app = document.getElementById('app')
    function routerView(localHash) {
      // 去 routes 数组中查找 localHash 值在哪一项中
      // 如果找到了,就将这一项中的component执行结果放到 app 容器中
      const index = routes.findIndex((item) => {
        return '#' + item.path === localHash
      })
      app.innerHTML = routes[index].component()
    }

    // 监听页面加载完毕
    window.addEventListener('DOMContentLoaded', () => {
      routerView(location.hash)
    })
    // 浏览器自带的一个监听hash变化事件
    window.addEventListener('hashchange', () => {
      // console.log(location.hash);// 获取到url变化
      routerView(location.hash)
    })

  </script>
</body>
</html>

3. history路由实现

通过上文我们知道了history路由的路径中是没有#的,这样的话我们如果变换路径的时候那就必然会引起页面的刷新,我们实现history路由的时候要避免这一点。

首先我们页面中的基础html结构同上,我们同样需要一个routerView()函数来实现对应url组件加载功能:

const app = document.getElementById('app')
function routerView() {
  const index = routes.findIndex(item => {
    return item.path === location.pathname // 可以读取本地url
  })
  app.innerHTML = routes[index].component()
}

接下来我们想要实现的效果是点击对应的a标签就展示对应的组件,我们在点击a标签的时候就会进行a标签的默认跳转事件,这个时候我们只需要对要实现router效果的a标签阻止它的默认行为,然后修改url路径后执行routerView()函数即可:

history.pushState() 方法可以修改url,不会带来页面刷新

// 获取页面中所有的a标签
let linkList = document.querySelectorAll('a')
linkList.forEach(link => {
  link.addEventListener('click', function(e) {
    // 让a标签不跳转,阻止默认行为
    e.preventDefault()
    // 让 url 发生变化
    history.pushState(null, '', this.getAttribute('href')) 
    routerView()    
  })
})

在实现了url变更展示对应组件和页面不刷新两点功能之后,还有一个小点得注意一下,当我们点击浏览器左上角前进后退的时候,页面也要展示对应url的组件,仅靠上述代码是实现不了的,这时候我们就需要使用'popstate'事件来实现这个效果,接下来直接给大家展示完整代码:

<!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="app"></div>

  <script>
    const routes = [
      {
        path: '/home',
        component: () => {
          return '<h2>首页页面</h2>'
        }
      },
      {
        path: '/about',
        component: () => {
          return '<h3>about页面</h3>'
        }
      },
    ]

    // 监听浏览器前进后退按钮
    window.addEventListener('popstate', function () {
      routerView()
    })

    let linkList = document.querySelectorAll('a')
    linkList.forEach(link => {
      link.addEventListener('click', function(e) {
        // 让a标签不跳转
        e.preventDefault()  // 阻止默认行为
        // 让 url 发生变化
        history.pushState(null, '', this.getAttribute('href')) // 可以修改url,不会带来页面刷新   
        routerView()    
      })
    })

    const app = document.getElementById('app')
    function routerView() {
      const index = routes.findIndex(item => {
        return item.path === location.pathname // 可以读取本地url
      })
      app.innerHTML = routes[index].component()
    }
  </script>
</body>
</html>

总结

hash 路由: #/home 在浏览器的 url 中出现 # 号,#后面的内容会被看作是一个 hash 值,hash 值的变化不会引起页面的重新加载,从而直接监听hashchange事件来判断要展示的对应的组件

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