资讯专栏INFORMATION COLUMN

自己动手实现一个前端路由

psychola / 723人阅读

摘要:单页面应用利用了动态变换网页内容避免了页面重载路由则提供了浏览器地址变化网页内容也跟随变化两者结合起来则为我们提供了体验良好的单页面应用前端路由实现方式路由需要实现三个功能浏览器地址变化切换页面点击浏览器后退前进按钮,网页内容跟随变化刷新浏

单页面应用利用了JavaScript动态变换网页内容,避免了页面重载;路由则提供了浏览器地址变化,网页内容也跟随变化,两者结合起来则为我们提供了体验良好的单页面web应用
前端路由实现方式

路由需要实现三个功能:

​ ①浏览器地址变化,切换页面;

​ ②点击浏览器【后退】、【前进】按钮,网页内容跟随变化;

​ ③刷新浏览器,网页加载当前路由对应内容

在单页面web网页中,单纯的浏览器地址改变,网页不会重载,如单纯的hash网址改变网页不会变化,因此我们的路由主要是通过监听事件,并利用js实现动态改变网页内容,有两种实现方式:

hash路由: 监听浏览器地址hash值变化,执行相应的js切换网页
history路由: 利用history API实现url地址改变,网页内容改变

hash路由

首先定义一个Router

class Router {
  constructor(obj) {
    // 路由模式
    this.mode = obj.mode
    // 配置路由
    this.routes = {
      "/index"                : "views/index/index",
      "/index/detail"         : "views/index/detail/detail",
      "/index/detail/more"    : "views/index/detail/more/more",
      "/subscribe"            : "views/subscribe/subscribe",
      "/proxy"                : "views/proxy/proxy",
      "/state"                : "views/state/stateDemo",
      "/state/sub"            : "views/state/components/subState",
      "/dom"                  : "views/visualDom/visualDom",
      "/error"                : "views/error/error"
    }
    this.init()
  }
}

路由初始化init()时监听load,hashchange两个事件:

window.addEventListener("load", this.hashRefresh.bind(this), false);
window.addEventListener("hashchange", this.hashRefresh.bind(this), false);

浏览器地址hash值变化直接通过a标签链接实现


hash值变化后,回调方法:

/**
 * hash路由刷新执行
 */
hashRefresh() {
  // 获取当前路径,去掉查询字符串,默认"/index"
  var currentURL = location.hash.slice(1).split("?")[0] || "/index";
  this.name = this.routes[this.currentURL]
  this.controller(this.name)
}
/**
  * 组件控制器
  * @param {string} name 
  */
controller(name) {
  // 获得相应组件
  var Component = require("../" + name).default;
  // 判断是否已经配置挂载元素,默认为$("#main")
  var controller = new Component($("#main"))
}

有位同学留言要实现路由懒加载,参考vue的实现方式,这里贴出来,希望大家多提意见:

  /**
   * 懒加载路由组件控制器
   * @param {string} name 
   */
  controller(name) {
    // import 函数会返回一个 Promise对象,属于es7范畴,需要配合babel的syntax-dynamic-import插件使用
    var Component = ()=>import("../"+name);
    Component().then(resp=>{
      var controller = new resp.default($("#main"))
    })
  }

考虑到存在多级页面嵌套路由的存在,需要对嵌套路由进行处理:

直接子页面路由时,按父路由到子路由的顺序加载页面

父页面已经加载,再加载子页面时,父页面保留,只加载子页面

改造后的路由刷新方法为:

hashRefresh() {
  // 获取当前路径,去掉查询字符串,默认"/index"
  var currentURL = location.hash.slice(1).split("?")[0] || "/index";  
  // 多级链接拆分为数组,遍历依次加载
  this.currentURLlist = currentURL.slice(1).split("/")
  this.url = ""
  this.currentURLlist.forEach((item, index) => {
    // 导航菜单激活显示
    if (index === 0) {
      this.navActive(item)
    }
    this.url += "/" + item
    this.name = this.routes[this.url]
    // 404页面处理
    if (!this.name) {
      location.href = "#/error"
      return false
    }
    // 对于嵌套路由的处理
    if (this.oldURL && this.oldURL[0]==this.currentURLlist[0]) {
      this.handleSubRouter(item,index)
    } else {
      this.controller(this.name)
    }
  });
  // 记录链接数组,后续处理子级组件
  this.oldURL = JSON.parse(JSON.stringify(this.currentURLlist))
}
/**
  * 处理嵌套路由
  * @param {string} item 链接list中当前项
  * @param {number} index 链接list中当前索引
  */
handleSubRouter(item,index){
  // 新路由是旧路由的子级
  if (this.oldURL.length < this.currentURLlist.length) {
    // 相同路由部分不重新加载
    if (item !== this.oldURL[index]) {
      this.controller(this.name)
    }
  }
  // 新路由是旧路由的父级
  if (this.oldURL.length > this.currentURLlist.length) {
    var len = Math.min(this.oldURL.length, this.currentURLlist.length)
    // 只重新加载最后一个路由
    if (index == len - 1) {
      this.controller(this.name)
    }
  }
}                

这样,一个hash路由组件就实现了

使用时,只需new一个Router实例即可:new Router({mode:"hash"})

history 路由

window.history属性指向 History 对象,是浏览器的一个属性,表示当前窗口的浏览历史,History 对象保存了当前窗口访问过的所有页面地址。更多了解History对象,可参考阮一峰老师的介绍: History 对象

webpack开发环境下,需要在devServer对象添加以下配置:

historyApiFallback: {
  rewrites: [
    { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, "index.html") },
  ],
}

history路由主要是通过history.pushState()方法向浏览记录中添加一条历史记录,并同时触发js回调加载页面

当【前进】、【后退】时,会触发history.popstate 事件,加载history.state中存放的路径

history路由实现与hash路由的步骤类似,由于需要配置路由模式切换,页面中所有的a链接都采用了hash类型链接,history路由初始化时,需要拦截a标签的默认跳转:

  /**
   * history模式劫持 a链接
   */
  bindLink() {
    $("#nav").on("click", "a.nav-item", this.handleLink.bind(this))
  }
 /**
   * history 处理a链接
   * @param  e 当前对象Event
   */
  handleLink(e) {
    e.preventDefault();
    // 获取元素路径属性
    let href = $(e.target).attr("href")
    // 对非路由链接直接跳转
    if (href.slice(0, 1) !== "#") {
      window.location.href = href
    } else {
      let path = href.slice(1)
      history.pushState({
        path: path
      }, null, path)
      // 加载相应页面
      this.loadView(path.split("?")[0])
    }
  }

history路由初始化需要绑定loadpopstate事件

this.bindLink()
window.addEventListener("load", this.loadView.bind(this, location.pathname));
window.addEventListener("popstate", this.historyRefresh.bind(this));

浏览是【前进】或【后退】时,触发popstate事件,执行回调函数

/**
  * history模式刷新页面
  * @param  e  当前对象Event
  */
historyRefresh(e) {
  const state = e.state || {}
  const path = state.path.split("?")[0] || null
  if (path) {
    this.loadView(path)
  }
}

history路由模式首次加载页面时,可以默认一个页面,这时可以用history.replaceState方法

if (this.mode === "history" && currentURL === "/") {
  history.replaceState({path: "/"}, null, "/")
  currentURL = "/index"
}

对于404页面的处理,也类似

history.replaceState({path: "/error"}, null, "/error")
this.loadView("/error")

点击预览

更多源码请访问Github

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108677.html

相关文章

  • 自己动手实现一个前端路由

    摘要:监听的变动省略其他代码省略其他代码这样,我们就初步实现了一个路由,那么接下来,我们来看看路由怎么实现。 前言 用过现代前端框架的同学,对前端路由一定不陌生, vue, react, angular 都有自己的 router, 那么你对 router 的工作原理了解吗?如果还不了解, 那么请跟我一起来手写一个简单的前端路由, 顺便了解一下. 实现路由的2种方式 hash模式 histo...

    longshengwang 评论0 收藏0
  • webpack+vue项目实战(三,配置功能操作页和组件的按需加载)

    摘要:但是实际上,回款管理和开票管理的组件文件也是加载了。所以下面引用按需加载来处理。是不是小很多了,然后和是按需加载的,就是需要的时候才加载。 1.前言 上篇文章(webpack+vue项目实战(二,开发管理系统主页面)),实现了,侧边栏的一个操作,点击侧边栏的一些操作,最重要的就是路由的切换。看了上一篇的伙伴也不难发现,除了点击侧边栏‘首页’之外,点击其它的都是白色的一片。原因我想大家都...

    endless_road 评论0 收藏0
  • 记一次vue仿网易云音乐的单页面应用

    摘要:说明一直想做一个基于的项目但是因为项目往往要涉及到后端的知识不会后端真的苦所以就没有一直真正的动手去做一个项目。直到发现上有网易云音乐的才开始动手去做。仅仅完成了首页登入,歌单,歌曲列表页。 说明 一直想做一个基于VUE的项目,但是因为项目往往要涉及到后端的知识(不会后端真的苦),所以就没有一直真正的动手去做一个项目。直到发现GitHub上有网易云音乐的api NeteaseCloud...

    hqman 评论0 收藏0
  • 使用next.js结合GITHUB ISSUE实现博客。

    摘要:而更多的应用采用的是简单的同构实现。请使用动态路由进行处理。后来用布署频繁调试,发现自定义在上并不能用,看建议使用动态路由。如果要取消这种行为可以使用方法。利用动态实现代码块切片。如果使用的话,建议使用动态路由去做布署啦。 使用next.js结合GITHUB ISSUE实现博客。 起因 。。。。起因是因为在某网站看到有一些类似实现。打算自己也做个side-project。 习惯性的对自...

    SillyMonkey 评论0 收藏0
  • JS或Jquery

    摘要:大潮来袭前端开发能做些什么去年谷歌和火狐针对提出了的标准,顾名思义,即的体验方式,我们可以戴着头显享受沉浸式的网页,新的标准让我们可以使用语言来开发。 VR 大潮来袭 --- 前端开发能做些什么 去年谷歌和火狐针对 WebVR 提出了 WebVR API 的标准,顾名思义,WebVR 即 web + VR 的体验方式,我们可以戴着头显享受沉浸式的网页,新的 API 标准让我们可以使用 ...

    CatalpaFlat 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<