Vue-前端路由

315 阅读9分钟

路由定义

网络路由的定义:网络原理中,路由指的是根据上一接口的数据包中的IP地址,查询路由表,路由表引导分组转送,然后转发到另一个接口,它决定的是一个端到端的网络路径

前端路由的定义:本质上就是检测url的变化,截获url地址,然后解析来匹配路由规则

路由原理

简单的说,就是在保证只有一个HTML页面,且与用户交互时不刷新和跳转页面的同时,为SPA单个网页应用中的每个视图展示形式匹配一个特殊的url。在刷新、前进、后退和SEO搜索优化时均通过这个特殊的 url 来实现。 为实现这一目标,我们需要做到以下三点:

  • 改变 url 且不让浏览器像服务器发送请求。
  • 可以监听到 url 的变化
  • 根据url跳转响应的页面或者组件

路由表

路由表是一个信息表格。在下面的代码中,路由根据查询路由表,通过路径可以跳转到不同的组件,即实现了path to component,也实现了分发

// Vue路由表
const router = new VueRouter({
  routes: [
    {
      path: '/index', // 路由的路径
      name: 'index',  // 路由名称,可选属性,定义后可以用其实现跳转
      component: { // 路由显示的组件
        template: '<div>index</div>'  // 组件模板
      }
    },
    {
      path: '/news',
      name: 'news',
      component: {
        template: '<div>news</div>'
      }
    },
    {
      path: '/user',
      name: 'user',
      component: {
        template: '<div>user</div>'
      }
    },
  ]
})

let vm = new Vue({
  el: '#app',
  data: {
  },
  // 将路由添加到Vue中
  router
})

// 触发Vue路由
<router-link class="nav" to="/index">index</router-link>

// React路由表
render((
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <Route path="about" component={About}/>
      <Route path="users" component={Users}>
        <Route path="/user/:userId" component={User}/>
      </Route>
      <Route path="*" component={NoMatch}/>
    </Route>
  </Router>
), document.body)

// 触发React路由
<Link to={`/user/89757`}>'joey'</Link>

默认路由

默认路由是指当我们进入应用,默认想显示某一个路由组件,或者当我们进入某一级路由组件的时候想默认显示其某一个子路由组件。以/开头的嵌套路径会被当作根路径

例如,当默认路由是当用户请求的地址为/根路径的时候,会有对应的反应,跳转至对应的视图。

404路由

404路由也称之为错误路由。例如,当请求不存在时,路由对应不到页面等情况时会出现报错

嵌套路由

嵌套路由是指多层路由嵌套, 一个路由里面包含多个子路由,层层嵌套

const router = new VueRouter({
  routes: [
    { path: '/user/:id', component: User,
      children: [
        {
          // 当 /user/:id/profile 匹配成功,
          // UserProfile 会被渲染在 User 的 <router-view> 中
          path: 'profile',
          component: UserProfile
        },
        {
          // 当 /user/:id/posts 匹配成功
          // UserPosts 会被渲染在 User 的 <router-view> 中
          path: 'posts',
          component: UserPosts
        }
      ]
    }
  ]
})

hash模式

定义:hash即地址栏URL中的#符号。hash值:即#符号及其符号后面的字符。例如 www.googlel.com/#hashhash ,其中 #hashhash就是hash值。hash模式是路由的默认模式。

原理:hash虽然出现URL中,事件但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。而且hash的改变会触发 hashchange, 浏览器的前进后退也能对其进行控制

hash模式优点

  • 兼容性更好,在老版IE中都能运行, 主流的前端路由方式

hash模式缺点

  • SEO搜索优化不友好,因为浏览器始终不会发送hash值给服务器,因此 baidu.com/#/tigerbaidu.com/#/lionbaidu.com 都是一样的,服务器收到的都是不带hash的地址,也就是 baidu.com

代码实例:类似原理实现

// HTML代码
<a href="#1">go to 1</a> <a href="#2">go to 2</a> <a href="#3">go to 3</a>
<a href="#4">go to 4</a>
<div id="app"></div>
<div id="div404" style="display: none;">你要找的内容被狗吃了</div>

// JS代码
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
};

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

  number = number || 1;

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

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

route(app);

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

history模式

定义:形如 /xxx的模式就是history模式,之所以叫history模式是因为浏览器的history API, 正常情况下由a链接发起的地址栏变化会使得页面重新刷新,带来不好的用户体验。而 history.pushState方法可以在不刷新页面的情况下更新浏览器地址栏,该方法不会触发任何事件,但是需要手动触发路由地址变化之后的页面渲染响应

原理:利用了HTML5 History Interface 中新增的pushState()replaceState()方法。

HTML5之前,浏览器就已经有了history对象。但在早期的history中只能用于多页面的跳转

history.go(-1);       // 后退一页
history.go(2);        // 前进两页
history.forward();     // 前进一页
history.back();      // 后退一页

HTML5的规范中,history新增了以下几个API

history.pushState();         // 在浏览历史中添加历史记录,但是并不触发跳转
history.replaceState();      // 修改浏览历史中当前纪录,而非添加记录,同样不触发跳转
history.state                // 返回当前状态对象
popstate                    //  每当同一个文档的浏览历史,即history对象出现变化时,就会触发popstate事件。

// 需要注意的是,仅仅调用pushState方法或replaceState方法 ,并不会触发popstate事件,只有用户点击浏览器倒退按钮和前进按钮,或者使用 JavaScript 调用back、forward、go方法时才会触发

history.pushState()history.replaceState()均接收三个参数(state, title, url)

  • state: 一个与指定网址相关的状态对象,popstate事件触发时,该对象会传入回调函数。如果不需要这个对象,此处可以填null
  • title: 新页面的标题,但是所有浏览器目前都忽略这个值,因此这里可以填null
  • url: 新的网址,必须与当前页面处在同一个域。浏览器的地址栏将显示这个网址

history.pushState()history.replaceState()的区别

  • history.pushState()在保留现有历史记录的同时,将url加入到历史记录中
  • history.replaceState()会将历史记录中的当前页面历史替换为url

history模式的路由过程

由于history.pushState()history.replaceState()可以改变 url 同时,不会刷新页面,所以在 HTML5中的histroy具备了实现前端路由的能力。而在hash模式,当hash变化时,可以通过hashchange进行监听。而history的改变并不会触发任何事件,所以我们无法直接监听history的改变而做出相应的改变。

所以,我们需要换个思路,我们可以罗列出所有可能触发history 改变的情况,并且将这些方式一一进行拦截,变相地监听history的改变。 对于单页应用的history模式而言,url的改变只能由下面四种方式引起:

  • 点击浏览器的前进或后退按钮
  • 点击a标签
  • JS代码中触发history.pushState函数
  • JS代码中触发history.replaceState函数

history模式优点

  • 后端将所有前端路由都渲染到同一页面
  • 可以进行SEO搜索优化

history模式缺点

  • 不支持IE8以下的版本
  • 由于浏览器地址是通过history.pushState() 更新的,当用户手动刷新页面时,需要有一个机制保证返回的页面仍然是当前页面
  • 需要后端支持,每次返回html文档 代码实例:类似原理实现
// HTML代码
<a class="link" href="/1">go to 1</a> <a class="link" href="/2">go to 2</a>
<a class="link" href="/3">go to 3</a> <a class="link" href="/4">go to 4</a>
<div id="app"></div>

<div id="div404" style="display: none;">你要找的内容被狗吃了</div>

// JS代码
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
};

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 => {
    e.preventDefault();
    const href = a.getAttribute("href");
    window.history.pushState(null, `page ${href}`, href);
    // 通知
    onStateChange(href);
  });
}

route(app);

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

memory模式

定义:在内存中维护一个堆栈用于管理访问历史的方式,比较复杂。早期移动端使用比较多。实现麻烦,问题也较多,现在很少有使用。React Native使用这种路由模式。

原理:hashhistory模式都是将地址信息保存在url中,可以明显的感受到路由的过程。而在 app开发中不存在浏览器的概念。memory模式就是把路由信息存到一个对象里面,(比如 localStorage)每次读取当前的路由地址展示对应的页面。由于路由信息无法体现到url 中,因此拷贝链接分享的时候,可能会导致看到的渲染内容不一样的问题。

// HTML代码
<a class="link" href="/1">go to 1</a> <a class="link" href="/2">go to 2</a>
<a class="link" href="/3">go to 3</a> <a class="link" href="/4">go to 4</a>
<div id="app"></div>

<div id="div404" style="display: none;">你要找的内容被狗吃了</div>

// JS代码
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
};

function route(container) {
  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);
  });
}

route(app);

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

memory模式优点

  • 不存在兼容性问题,路由保存在内存中
  • 不需要服务器端提供支持

memory模式缺点

  • 目前很少有前端路由模块提供对memory路由的实现(react-router提供了memory实现)
  • 自己实现难度较大,且工作量也很大
  • 对于前进后退操作的路由管理非常麻烦,尤其是android设备,适合非浏览器应用

更多信息

vue-Router源码

前端路由是什么

「前端进阶」彻底弄懂前端路由

vue-router深度解析,全方位搞定路由!

MDN history