路由hash和history的实现

416 阅读4分钟

路由最早是用来描述服务器上的资源路径。

  • 单页应用:整个项目就算有很多个页面,也只有一个html文件,每个页面被处理成组件都移交到这一个html文件里面来展示。其中路由通常是通过前端JavaScript来实现的。当点击链接或输入URL时,前端路由器会根据路径匹配相应的组件

  • 多页应用:有很多html,每个页面对应一个html文件。其中路由通常是由服务器端来控制的。当请求一个URL时,服务器会根据请求的路径返回相应的HTML页面

前端路由

前端路由就是通过 url 路径来匹配想要展示的内容也就是代码块的这么一套机制。在单页应用中,点击跳页面浏览器是不会重新刷新的。

要实现前端路由就需要解决:

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

我们要改变 url 地址栏就需要用到<a href="/home">首页</a>的标签,但是这个点击就会刷新浏览器,在整个html中,但是只有a标签的href属性可以修改浏览器的地址栏。但是如果在路径前面加#号,如<a href="#/home">首页</a>,这样浏览器就不会刷新了,这是浏览器本身就有的机制,在浏览器眼里,只要url地址栏出现#,其后面所有出现的内容都会被认定为一串hash值,而hash值的变更不会引起浏览器的刷新的。

hash 路由

#/home 在浏览器的url中出现 # 号,# 后面的内容会被看作是一个 hash 值,hash 值得变化不会引起页面的重新加载。然后就是监听url的hash值变化,让对应代码块出现。创建一个数组,里面每项是一个对象,分别有

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

  <div id="app"></div>


  <script>
    const routes = [
      {
        path: "/home",
        component: () => {
          return "首页页面"
        }
      },
      {
        path: "/about",
        component: () => {
          return "关于页面"
        }
      }
    ]
    const app = document.getElementById("app");
    window.addEventListener("hashchange", () => {
      const localhash = window.location.hash;
      console.log(localhash);   // #home

      routerView(localhash)
    })
    
    // 地址栏如果刷新重新调用routerView
    window.addEventListener("DOMContentLoaded", () => {
      routerView(location.hash)
    })

    function routerView(localhash) {
      // 去 routes 数组中查找 localhash 值在哪一项中
      // 找到了就返回这一项中的 component 执行结果放进 app 中
      const index = routes.findIndex((item) => {
        return '#' + item.path === localhash
      })
      const content = routes[index].component()
      app.innerHTML = content
    }
  </script>
</body>
</html>

主要思路就是在a标签中使用#号,使得浏览器不刷新,然后定义一个路由数组,里面每一项为一个对象,对象中的属性为path路径,component要展示的代码块,然后监听hashchangehash值改变事件,使用window.location.hash获取当前hash值,然后遍历数组从里面找到对应的代码块将其显示到页面,最后需注意到浏览器刷新时,hash不变,但是页面会重新刷新,还要监听DOMContentLoaded页面加载完毕事件。hash后面的为无效地址。

history 路由

a标签默认点击会跳转,就需要首先禁止掉a标签的默认跳转行为e.preventDefault(),然后使用 history.pushState(null,'',this.getAttribute('href')) ,来改变 url 路径,history是浏览器环境下独有的一个对象(栈结构),用来管理浏览器的前进和后退。然后使用loacltion.pathname来读取本地url地址路径。然后判断拿取代码块去展示。最后使用监听popstate浏览器前进后退按钮,

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

  <div id="app"></div>

  <script>
    const routes = [
      {
        path: '/home',
        component: () => {
          return "<h1>首页页面</h1>"
        }
      },
      {
        path: '/about',
        component: () => {
          return "<h1>about页面</h1>"
        }
      },
    ]
    
    // 监听浏览器前进后退按钮
    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'))
        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
      })
      app.innerHTML = routes[index].component()
    }
  </script>
</body>

</html>

小结

  • hash 路由#/home 在浏览器的url中出现 # 号,# 后面的内容会被看作是一个 hash 值,hash 值得变化不会引起页面的重新加载,从而直接监听 hashchange 来判断要展示对应的组件
  • history 路由:首先阻止 a 标签的默认跳转行为,然后使用 history.pushState() 来改变url路径,它是不会引起页面的重新加载的,在 url 变更后,读取本地的 pathname 来判断要展示的对应的组件。考虑到浏览器的前进后退按钮的存在,需要监听 popstate 事件(前进后退事件),来判断要展示的对应组件。