Vue Router原理

2,495 阅读6分钟

一.如何切换页面

我们能在同一个网址上访问不同的页面正是用到了路由功能,这个路由地址能通过window.location.hash得到,以Vue官网为例,打开一个页面查询此属性可以得到

window.location.hash
"#router-start-replaced"

这个值是自带一个#号的

那么我们如何监听到route的变化呢,有一个api,hashchange

window.addEventListener("hashchange", () => {
  console.log("hash 变了");
});

那么我们只需要监听hashchange事件,如何将相应的页面展示出来就好了

二. 路由表

我们得到了window.location.hash的值以后,如何将它跳转到相应的页面呢,这就需要一个hash与页面的映射表,这就是路由表

const app = document.querySelector("#app");
const div1 = document.createElement("div");
div1.innerHTML = "1";
const div2 = document.createElement("div");
div2.innerHTML = "2";
const div3 = document.createElement("div");
div3.innerHTML = "3";
const div4 = document.createElement("div");
div4.innerHTML = "4";
const routeTable = {
  "1": div1,
  "2": div2,
  "3": div3,
  "4": div4 
};

这里我们创建几个DOM节点,然后在路由表中建立映射

三. router

接下来我们就可以来写一个router了


function route(container) {
  let number = window.location.hash.substr(1);

  number = number || 1;
  //默认路由,当hash值为空时将它设为1

 //根据hash得到路由表中相应页面
  let div = routeTable[number.toString()];
  //如果路由表中不存在则跳转到404页面
  if (!div) {
    div = document.querySelector("#div404");
  }
  div.style.display = "block";

  //先将容器中清空
  container.innerHTML = "";
  //展示内容
  container.appendChild(div);
}

router(app)

最后配合上面的事件监听,当hash值变化就路由一次

window.addEventListener("hashchange", () => {
    route(app);
  });

自此一个简单的单页面路由就完成了

代码

四. 路由嵌套

如果一级路由下还有子代元素那么可再创建一个字路由表

const routeTable = {
  "1/1": div11,
  "1/2": div12,
  "1/3": div13,
  "1/4": div14,
};

然后在router函数中加入一定的条件判断,比如用正则匹配然后分发到相应的页面

五.history

上述的hash模式,我们的请求路径自带一个#,history模式则是自带一个/ 我们可以通过window.location.pathname来得到这个值,路由表也相应更改

const routeTable = {
  "/1": div1,
  "/2": div2,
  "/3": div3,
  "/4": div4
};

hash对SEO不友好,因为hash部分会被浏览器直接忽略,因此服务器得不到这部分数据,而history模式IE8以下浏览器不支持

使用history也有个问题那就是每次请求都会重新刷新页面因此会变得很慢,所以我们要对他进行改进

window.history.pushState(stateObj, "page 2", "bar.html");
//1. 状态对象 2. 标题 3. url

这个api可以可以改变referrer,它在用户发送 XMLHttpRequest 请求时在HTTP头部使用,改变state后创建的 XMLHttpRequest 对象的referrer都会被改变。因为referrer是标识创建 XMLHttpRequest 对象时 this 所代表的window对象中document的URL。

注意 pushState() 绝对不会触发 hashchange 事件

然后我们如何监听这个变化呢,答案是不同监听,直接得到路径然后重新router

function route(container) {
  let number = window.location.pathname;
  console.log("number: " + number);
  if (number === "/") {
    number = "/1";
  }

  // 获取界面
  let div = routeTable[number.toString()];
  if (!div) {
    div = document.querySelector("#div404");
  }
  div.style.display = "block";

  // 展示界面
  container.innerHTML = "";
  container.appendChild(div);
}
const allA = document.querySelectorAll("a.link");

for (let a of allA) {
  a.addEventListener("click", e => {
  //组织a标签的默认跳转动作
    e.preventDefault();
    const href = a.getAttribute("href");
    window.history.pushState(null, `page ${href}`, href);
    // 直接调用一个函数重新router
    onStateChange(href);
  });
}

route(app);

function onStateChange() {
  console.log("state 变了");
  route(app);
}

六.memory

我们将上述的history模式路劲存放地址放到localStorage中会怎样呢

function route(container) {
//从localStorage中取路径,默认为1
  let number = window.localStorage.getItem("xxx");

  if (!number) {
    number = "/1";
  }

  // 获取界面
  let div = routeTable[number.toString()];
  if (!div) {
    div = document.querySelector("#div404");
  }
  div.style.display = "block";

  // 展示界面
  container.innerHTML = "";
  container.appendChild(div);
}

const allA = document.querySelectorAll("a.link");

for (let a of allA) {
  a.addEventListener("click", e => {
    e.preventDefault();
    const href = a.getAttribute("href");
    //点击之后改变本地中的路径
    window.localStorage.setItem("xxx", href);
    onStateChange(href);
  });
}

这样的好处是,每次刷新页面后,总是会渲染出上一次退出的页面,因为这部分是存在本地上的,就像有了记忆

history模式和hash模式的区别

hash

1. #

# 代表网页中的一个位置。其右边的字符,就是该位置的标识符。比如, http://www.example.com/index.html/#/print 就代表网页 index.html 的 print 位置。浏览器读取这个 url 后,

2. 设置#

1. 锚点

<a name="print"></a>

2. id

<div id="print">

3. 特点

hash出现在URL中,但是并不会被带入HTTP请求,因此后端接收到的还是原始路径http://www.example.com/index.htm

4. 获取

window.location.hash可以得到当前的hash值

读取时,可以用来判断网页状态是否改变

写入时,则会在不重载网页的前提下,创造一条访问历史记录。

5. 监听

window.addEventListener('hashchange', function(){}

可以通过hashchange事件来监听hash值变化

history

1. 兼容性

hash 能兼容到IE8, history 只能兼容到 IE10

2. 参数传递

hash模式原本的作用是来跳转元素所在的锚点的,如果将其作为路由,则锚点的作用无法兼容,切hash模式的参数完全依靠#所带的参数

history则不仅可以在url中填写参数,还有一个window.history对象可以访问,上面可以承载更多的参数

3. 跳转

//参数为-1,作用等价
window.history.back();
window.history.go(-1);
window.history.forward();
window.history.go(1);
//等等
window.history.go(2)

4. 修改历史

HTML5引入了 history.pushState()history.replaceState() 方法,它们分别可以添加和修改历史记录条目。这些方法通常与window.onpopstate 配合使用。

window.history.pushState(state, title, url) 
// state:需要保存的数据,这个数据在触发popstate事件时,可以在event.state里获取
// title:标题,基本没用,一般传 null
// url:设定新的历史记录的 url。新的 url 与当前 url 的 origin 必须是一樣的,否则会抛出错误。url可以是绝对路径,也可以是相对路径。
//如 当前url是 https://www.baidu.com/a/,执行history.pushState(null, null, './qq/'),则变成 https://www.baidu.com/a/qq/,
//执行history.pushState(null, null, '/qq/'),则变成 https://www.baidu.com/qq/
 
window.history.replaceState(state, title, url)
// 与 pushState 基本相同,但她是修改当前历史记录,而 pushState 是创建新的历史记录
 
window.addEventListener("popstate", function() {
    // 监听浏览器前进后退事件,pushState 与 replaceState 方法不会触发              

通过pushstate把页面的状态保存在state对象中,当页面的url再变回这个url时,可以通过event.state取到这个state对象,从而可以对页面状态进行还原,这里的页面状态就是页面字体颜色,其实滚动条的位置,阅读进度,组件的开关的这些页面状态都可以存储到state的里面。

5. 后端支持

当我们通过history模式更改url时,由于url改变,所以浏览器会重新向服务器发送页面请求,因此,后端应该将其他的url请求都返回同一个html页面,这样浏览器就会发现,服务器返回内容与当前页面相同,则不会重新渲染页面

同样的,当我们直接访问http://www.example.com/index.htm时,如果后端未做相应处理,路由匹配不到任何资源,则跳转到404页面,因此history需要后端支持才能实现