0

    前端路由跳转基本原理

    2023.07.29 | admin | 123次围观

    1. # http://sherlocked93.club/base/#/page1

    2. {

    3.  "href": "http://sherlocked93.club/base/#/page1",

    4.  "pathname": "/base/",

    5.  "hash": "#/page1"

    6. }

    注意: Hash 方法是利用了相当于页面锚点的功能,所以与原来的通过锚点定位来进行页面滚动定位的方式冲突,导致定位到错误的路由路径,因此需要采用别的办法,之前在写 progress-catalog 这个插件碰到了这个情况。

    1.2 实例

    这里简单做一个实现,原理是把目标路由和对应的回调记录下来,点击跳转触发 hashchange 的时候获取当前路径并执行对应回调浏览器回退按钮事件,效果:

    1. class RouterClass {

    2.  constructor() {

    3.    this.routes = {}        // 记录路径标识符对应的cb

    4.    this.currentUrl = ''    // 记录hash只为方便执行cb

    5.    window.addEventListener('load', () => this.render())

    6.    window.addEventListener('hashchange', () => this.render())

    7.  }


    8.  /* 初始化 */

    9.  static init() {

    10.    window.Router = new RouterClass()

    11.  }


    12.  /* 注册路由和回调 */

    13.  route(path, cb) {

    14.    this.routes[path] = cb || function() {}

    15.  }


    16.  /* 记录当前hash,执行cb */

    17.  render() {

    18.    this.currentUrl = location.hash.slice(1) || '/'

    19.    this.routes[this.currentUrl]()

    20.  }

    21. }

    具体实现参照 CodePen

    如果希望使用脚本来控制 Hash 路由的后退,可以将经历的路由记录下来,路由后退跳转的实现是对 location.hash 进行赋值。但是这样会引发重新引发 hashchange 事件,第二次进入 render 。所以我们需要增加一个标志位浏览器回退按钮事件,来标明进入 render 方法是因为回退进入的还是用户跳转

    1. class RouterClass {

    2.  constructor() {

    3.    this.isBack = false

    4.    this.routes = {}        // 记录路径标识符对应的cb

    5.    this.currentUrl = ''    // 记录hash只为方便执行cb

    6.    this.historyStack = []  // hash栈

    7.    window.addEventListener('load', () => this.render())

    8.    window.addEventListener('hashchange', () => this.render())

    9.  }


    10.  /* 初始化 */

    11.  static init() {

    12.    window.Router = new RouterClass()

    13.  }


    14.  /* 记录path对应cb */

    15.  route(path, cb) {

    16.    this.routes[path] = cb || function() {}

    17.  }


    18.  /* 入栈当前hash,执行cb */

    19.  render() {

    20.    if (this.isBack) {      // 如果是由backoff进入,则置false之后return

    21.      this.isBack = false   // 其他操作在backoff方法中已经做了

    22.      return

    23.    }

    24.    this.currentUrl = location.hash.slice(1) || '/'

    25.    this.historyStack.push(this.currentUrl)

    26.    this.routes[this.currentUrl]()

    27.  }


    28.  /* 路由后退 */

    29.  back() {

    30.    this.isBack = true

    31.    this.historyStack.pop()                   // 移除当前hash,回退到上一个

    32.    const { length } = this.historyStack

    33.    if (!length) return

    34.    let prev = this.historyStack[length - 1]  // 拿到要回退到的目标hash

    35.    location.hash = `#${ prev }`

    36.    this.currentUrl = prev

    37.    this.routes[prev]()                       // 执行对应cb

    38.  }

    39. }

    代码实现参考 CodePen

    2. HTML5 History Api2.1 相关 Api

    HTML5 提供了一些路由操作的 Api,关于使用可以参看 这篇 MDN 上的文章,这里就列举一下常用 Api 和他们的作用,具体参数什么的就不介绍了,MDN 上都有

    history.go(n):路由跳转,比如n为2是往前移动2个页面,n为-2是向后移动2个页面,n为0是刷新页面

    history.back():路由后退,相当于history.go(-1)

    history.forward():路由前进,相当于history.go(1)

    history.pushState():添加一条路由历史记录,如果设置跨域网址则报错

    history.replaceState():替换当前页在路由历史记录的信息

    popstate事件:当活动的历史记录发生变化,就会触发popstate事件,在点击浏览器的前进后退按钮或者调用上面前三个方法的时候也会触发,参见MDN

    2.2 实例

    将之前的例子改造一下,在需要路由跳转的地方使用 history.pushState 来入栈并记录 cb,前进后退的时候监听 popstate 事件拿到之前传给 pushState 的参数并执行对应 cb,因为借用了浏览器自己的 Api,因此代码看起来整洁不少

    1. class RouterClass {

    2.  constructor(path) {

    3.    this.routes = {}        // 记录路径标识符对应的cb

    4.    history.replaceState({ path }, null, path)    // 进入状态

    5.    this.routes[path] && this.routes[path]()

    6.    window.addEventListener('popstate', e => {

    7.      const path = e.state && e.state.path

    8.      this.routes[path] && this.routes[path]()

    9.    })

    10.  }


    11.  /* 初始化 */

    12.  static init() {

    13.    window.Router = new RouterClass(location.pathname)

    14.  }


    15.  /* 注册路由和回调 */

    16.  route(path, cb) {

    17.    this.routes[path] = cb || function() {}

    18.  }


    19.  /* 跳转路由,并触发路由对应回调 */

    20.  go(path) {

    21.    history.pushState({ path }, null, path)

    22.    this.routes[path] && this.routes[path]()

    23.  }

    24. }

    Hash 模式是使用 URL 的 Hash 来模拟一个完整的 URL,因此当 URL 改变的时候页面并不会重载。History 模式则会直接改变 URL,所以在路由跳转的时候会丢失一些地址信息,在刷新或直接访问路由地址的时候会匹配不到静态资源。因此需要在服务器上配置一些信息,让服务器增加一个覆盖所有情况的候选资源,比如跳转 index.html 什么的,一般来说是你的 app 依赖的页面,事实上 vue-router 等库也是这么推介的,还提供了常见的服务器配置。

    代码实现参考 CodePen

    网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~

    参考:

    history \| MDN

    hashchange \| MDN

    Manipulating the browser history \| MDN

    前端路由的基本原理 - 大史不说话

    History 对象 -- JavaScript 标准参考教程

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    标签: 路由
    发表评论