前端路由与单页页面实践

更新日期: 2022-03-22阅读: 738标签: 路由

路由就是指随着浏览器地址栏的变化,展示给用户的页面也不相同。

传统的网页根据用户访问的不同的地址,浏览器从服务器获取对应页面的内容展示给用户。这样容易造成服务器压力比较大,而且用户访问速度也比较慢,在这种场景下,出现了单页应用。

路由的实现方式

  1. location.hash+hashchange事件。
  2. history.pushState()+popState事件。

实现主要基于以下几个方面的特性

  1. URL 中的 hash 值只是客户端的一种状态,也就是说当向服务器发出请求时,hash 部分不会被发送。
  2. hash 值的改变,都会在浏览器的访问历史中增加一个记录,因此我们能通过浏览器的回退,前进按钮控制 hash 的切换。
  3. 可以通过设置a标签,并通过设置 href 属性,例如href = ‘#/blue’,当点击标签的时候,url的 hash 。值会发生改变,在当前url的后面增加上’#/blue’, 同时触发hashchange,再回调函数中进行处理。
  4. 前进后退的时候,可以直接通过js来对 location.hash 进行赋值,改变url的 hash 值,例如 location.hash = ‘#/blue’即可,此时url会改变, 也会触发hashchange事件。
  5. 因此我们可以使用 hashchange 事件来监听 hash 值得变化,从而对页面进行跳转(渲染)。

hash模式

hash方法是在路由中带有一个#,主要原理是通过监听#后的 URL 路径标识符的更改而触发的浏览器hashchange事件,然后通过获取location.hash 得到当前的路径标识符,再进行一些路由跳转的操作。hash方法的push本身会记录你的点击记录,当你想要通过返回按钮返回时,它会根据你的点击记录用到replace来解决。


先建立一个index.html文件,在body标签中开始hash的编写:

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>前端路由</title>
</head>
<body>
  <h1>hello , Hash router!!!</h1>
  <div style="margin-bottom:100px">
    <ul>
      <li><a href="#/">turn white</a></li>
      <li><a href="#/blue">turn blue</a></li>
      <li><a href="#/green">turn green</a>
      <li><a href="#/red">turn red</a></li>
      <li><a href="#/orange">turn orange</a>
    </ul>
    <button id="btn">回退</button>
    <div id="app" style="margin-top:50px;height:100px">

    </div>
  </div>
  <script>
    function render() {
      app.innerHTML = '渲染容器' + window.location.hash;
      app.style.backgroundColor = "pink"
    }
    window.addEventListener('hashchange', render)
  </script>
  <script src="./hash.js"></script>
</body>

</html>

然后引用js文件处理router里面的逻辑:

// hash.js
class Router {
  constructor() {
    /**
     * 以键值对的形式存储路由
     */
    this.routers = new Object();
    /**
     * 当前路由的URL
     */
    this.currentUrl = "";
    /**
     * 记录出现过的hash
     */
    this.history = [];
    /**
     * 作为指针,默认指向this.history的末尾,根据后退前进指向history中不同的hash
     */
    this.currentIndex = this.history.length - 1;
    /**
     * 默认不是后退操作
     */
    this.isBack = false;
  }
  /**
   * 都定义在原型上,后面的覆盖前面的,这个不执行
   */
  route(path, callback) {
    console.log(1);
  }
}
/**
 * 将路由的hash以及对应的callback函数储存
 * @param {*} path
 * @param {*} callback
 */
Router.prototype.route = function (routes) {
  for (let route of routes) {
    this.routers[route.path] = route.callback || function () { };
  }
};

/**
 * 当页面刷新的时候
 */
Router.prototype.refresh = function () {
  /**
   * 获取当前页面中的hash路径
   */
  this.currentUrl = window.location.hash.slice("1") || "/";
  /**
   * 不是后退才执行
   */
  if (!this.isBack) {
    if (this.currentIndex < this.history.length - 1)
      this.history = this.history.slice(0, this.currentIndex + 1);
    /**
     * 将当前hash路由推入数组储存,指针向前移动
     */
    this.history.push(this.currentUrl);
    this.currentIndex++;
  }
  this.isBack = false;
  /**
   * 执行当前hash路径的回调函数
   */
  this.routers[this.currentUrl]();
  console.log("refresh");
  console.log(this.history);
  console.log(this.currentIndex);
};
/**
 * 当页面后退,回退的过程中会触发hashchange,将hash重新放入,索引增加
 */
Router.prototype.back = function () {
  console.log("back");
  console.log(this.history);
  console.log(this.currentIndex);
  // 后退操作设置为true
  this.isBack = true;
  /**
   * 如果指针小于0的话就不存在对应hash路由了,因此锁定指针为0即可
   */
  this.currentIndex <= 0
    ? (this.currentIndex = 0)
    : (this.currentIndex = this.currentIndex - 1);
  /**
   * 随着后退,location.hash也应该随之变化
   * 并执行指针目前指向hash路由对应的callback
   */
  location.hash = `#${this.history[this.currentIndex]}`;
  this.routers[this.history[this.currentIndex]]();
};
/**
 * 初始化,监听页面的加载与hash值的变化
 */
Router.prototype.init = function () {
  /**
   * 修改this指向,否则指向window
   */
  window.addEventListener("load", this.refresh.bind(this), false);
  window.addEventListener("hashchange", this.refresh.bind(this), false);
};
// 监听hash模式路由
Router.prototype.eventHashRouter = function() {
  // 监听load事件,防止刷新页面数据丢失
  window.addEventListener("load", this.hashRouter.bind(this));
  window.addEventListener("hashchange", this.hashRouter.bind(this))
}
//replace模式页面跳转
Router.prototype.replace = function(url) {
  url = "#" +url;
  window.location.replace(url);
}
const route = new Router();
/**
 * 初始化
 */
route.init();
const routes = [
  {
    path: "/",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "#fff";
    },
  },
  {
    path: "/blue",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "blue";
    },
  },
  {
    path: "/green",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "green";
    },
  },
  {
    path: "/red",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "red";
    },
  },
  {
    path: "/orange",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "orange";
    },
  },
];
/**
 * 将hash值与cb绑定
 */
route.route(routes);
window.onload = function () {
  let btn = document.getElementById("btn");
  btn.addEventListener("click", route.back.bind(route), false);
};

history模式

Historyapi来实现URL的变化,其中最主要用history.pushState()新增一个历史记录,用history.replaceState()直接替换当前历史记录,可以在不进行刷新的情况下,操作浏览器的历史记录。需要后台配置支持,因为我们的应用是个单页的客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问一些没有配置的路径就会返回404,但因为没有#号,所以当用户刷新页面之类的操作时,浏览器还是会给服务器发送请求。为了避免出现这种情况,所以这个实现需要服务器的支持,需要定向到根路径。html5 提供了historyAPI来实现URL的变化,其中最主要的 API 有以下两个:

  1. history.pushState() 新增一个历史记录。
  2. history.replaceState() 直接替换当前历史记录。
  3. 相同点: 可以在不进行刷新的情况下,操作浏览器的历史记录。
  4. 先建立一个index.html文件,在body标签中开始history的编写:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>前端路由</title>
</head>
<body>
  <h1>hello , History router!!!</h1>
  <ul>
    <li><a href="/">turn white</a></li>
    <li><a href="http://127.0.0.1:5500/color/blue">turn blue</a></li>
    <li><a href="http://127.0.0.1:5500/color/green">turn green</a>
    <li><a href="http://127.0.0.1:5500/color/red">turn red</a></li>
    <li><a href="http://127.0.0.1:5500/color/orange">turn orange</a>
  </ul>
  <script>  
    function handleClick(e) {
      // e.target.href = "/color/blue"
      // console.log(this.location.host);
    }
  </script>
  <script src="./history.js"></script>
</body>

</html>

然后引用js文件处理router里面的逻辑:

// history.js
/**
 * history路由
 */
class Router {
  constructor() {
    /**
     * 以键值对的形式存储路由
     */
    this.routers = new Object();
  }
}

/**
 * 监听页面的popstate事件
 */
Router.prototype.bindPopState = function (e) {
  const path = e.state && e.state.path;
  this.routers[path] && this.routers[path]();
};

/**
 * 将路由的path以及对应的callback函数储存
 * @param {*} path
 * @param {*} callback
 */
Router.prototype.route = function (routes) {
  for (let route of routes) {
    this.routers[route.path] = route.callback || function () { };
  }
};

/**
 * 初始化,直接替换当前历史纪录,并用状态对象进行存储
 */
Router.prototype.init = function (path) {
  window.history.replaceState({ path: path }, null, path);
  this.routers[path] && this.routers[path]();
  /**
   * 加入事件监听
   */
  window.addEventListener("popstate", this.bindPopState.bind(this), false);
};

/**
 * 更新页面,新增一个历史纪录
 */
Router.prototype.go = function (path) {
  window.history.pushState({ path: path }, null, path);
  this.routers[path] && this.routers[path]();
};

const route = new Router();
route.init(window.location.href);
const routes = [
  {
    path: "http://127.0.0.1:5500/",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "#fff";
    },
  },
  {
    path: "http://127.0.0.1:5500/color/blue",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "blue";
    },
  },
  {
    path: "http://127.0.0.1:5500/color/green",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "green";
    },
  },
  {
    path: "http://127.0.0.1:5500/color/red",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "red";
    },
  },
  {
    path: "http://127.0.0.1:5500/color/orange",
    callback: function () {
      let el = document.body;
      el.style.backgroundColor = "orange";
    },
  },
];
/**
 * 将hash值与cb绑定
 */
route.route(routes);
/**
 * a标签会跳转页面,阻止
 */
window.addEventListener(
  "click",
  function (e) {
    var e = e || window.event;
    var target = e.target || e.srcElement;
    if ((target.tagName = "A")) {
      e.preventDefault();
      route.go(e.target.getAttribute("href"));
    }
  },
  false
);

总结

本文讲解了路由的核心实现原理,但是结合具体框架后,框架增加了很多特性,如动态路由、路由参数、路由动画等等,这些导致路由实现变的复杂。本文去粗取精只针对前端路由最核心部分的实现进行分析,并基于 hash 和 history 两种模式,页面加载时,它可能有一个非空状态对象。例如,如果页面设置了一个状态对象(使用pushState()or replaceState())然后用户重新启动他们的浏览器,当页面重新加载时,页面将收到一个onload事件,虽没有popstate事件,但是将获得加载的状态对象。

链接: https://www.fly63.com/article/detial/11282

vue路由history模式_如何去除vue项目中的#

在使用vue-cli搭建的环境中,浏览器上URL地址中是存在#的,这是由于vue-router 默认 hash 模式,不难发现#的出现真的很丑陋。官网给出了如何使用history模式mode: history

vue路由传参主要的3种方式

vue中路由传参主要的3种方式:query方式(push时使用path来匹配)、params模式(push时使用name来匹配)、location预声明参数模式(push使用path来匹配,但是它跟params模式不同)

vue动态加载路由_实现vue动态加载路由器设置

我们的通用的后台管理系统中,我们会根据权限的粗细不同,会对每个角色每个权限每个资源进行控制。同样的我们也需要实现一个这样的功能。 这篇文章我将主要讲vue端的实现,关于后台接口我就不会涉及,当我接触的时候我们的后台接口是springcloud实现。

两种前端路由的实现方式

前后端分离开发模式,后端会把路由控制丢在前端,这几天再开发单页面小的项目,手动撸了个路由。前端路由实现有两种方法。HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件:window.onpopstate。hash + location.onhashchange

vue动态路由_vue-router通过接口请求动态生成路由的实现

在后台管理系统中,一般都会采用权限管理。路由菜单数据都会保存到数据库中,在vue-router 2.2版本新增了一个router.addRoutes(routes)方法,即可用它来实现动态路由了

HTML5 History 模式

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

vue router 路由鉴权(非动态路由)

原本想用动态路由的思路去做,按权限加载对应路由表,但是由于权限可以交叉(比如一个人可以同时是主题管理员和数据服务管理员),导致权限路由表还是得去做判断组合。于是放弃了这个思路,索性就在beforeEach里直接判断了。

vue中路由按需加载的几种方式

使用vue-cli构建项目后,我们会在Router文件夹下面的index.js里面引入相关的路由组件,webpack在打包的时候会把整个路由打包成一个js文件,如果页面一多,会导致这个文件非常大,加载缓慢

vue-router 中参数传递(params,query)

query和params的区别,query相当于get请求,在页面跳转的时候,可以在地址栏看到请求参数,然而params则相当于post请求,参数不会在地址栏中显示。

Node.js的路由

当服务端接收到HTTP请求时,可以通过onRequest() 获取到url, pathname,query,及paramParams参数;为了解析这些数据需要使用url和querystring模块

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!