vue原理学习系列(五):路由__Vue.js
发布于 3 年前 作者 banyungong 1164 次浏览 来自 分享
粉丝福利 : 关注VUE中文社区公众号,回复视频领取粉丝福利

主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black, awesome-green, qklhk-chocolate

贡献主题:https://github.com/xitu/juejin-markdown-themes

theme: jzman highlight:

前言

vue原理学习系列,本期为主要学习下前端路由,以及实现简单的vue哈希路由和动态路由。

vue原理学习系列往期文章:

「内容速览」

  • 前端router介绍
  • 路由的实现原理
  • 哈希路由
  • 路由表
  • 动态路由

前端router介绍

随着前端应用的业务需求和功能变得愈发复杂,单页应用(SPA)已经成为前端应用的一种主流模式,SPA就是通过前端路由来实现的。

前端路由最核心的特点在于:通过改变URL,在不重新请求页面的情况下,对视图进行更新。

因此实现前端路由我们需要考虑两个问题:

  1. URL改变不引起页面刷新

  2. 对URL变化的监听

前端路由实现的2种主要方式:

  1. hash 实现:利用URL中hash(#)

  2. history 实现:利用History interface在HTML5中新增的方法

路由的实现原理

关于hash模式的路由和history模式的路由,vue:路由实现原理这篇文章可以说讲诉得十分详细。

我们对文章总结一下:

  • hash实现前端路由的原理

    • hash值是URL中#及后面连接它的一段字符串,通过window.location.hash获取。

    • hash用来指导浏览器动作,虽然出现在URL中,但不会被包括在http请求中,对服务器端来说没有作用的,因此hash的改变不会重新加载页面。

    • window对象可以监听hash改变时触发的haschange事件。

    • 每一次改变window.localtion.hash,都会在浏览器访问历史中增加一个记录。

  • history实现前端路由的原理

    • HTML5的History interface提供了2个新的方法:pushState(),replaceState(),通过这两个api可以让我们对浏览器历史记录栈进行修改。

    • window.history.pushState(state, title, url)

    • window.history,replaceState(state, title, url)

    • state:与pushState()创建的新历史记录条目相关联;title:新历史记录的标题;url:新历史记录的url。

    • 每当用户导航到新状态时,都会触发popstate事件。

    • pushState和replaceState共同的特点时当调用它们修改浏览器历史栈后,虽然当前url改变了,但浏览器不会刷新页面。

利用hash、history的特点,我们就可以实现前端路由的功能。

哈希路由

接下来我们模拟一个最简单的hash路由实现:

这里面关键点在于:

  1. 将当前URL的hash值在app(vue实例)中保存为响应性状态,让app响应hash值的变化
  2. 监听hash变化事件,改变app的hash值.
  3. 当app哈希值发生改变时,渲染不同的组件.
<div id="app">
  <component :is="url"></component>
  <a @click="routeTo('#foo')" href="#foo">foo</a>
  <a @click="routeTo('#bar')" href="#bar">bar</a>
</div>

<script>
window.addEventListener('hashchange', () => {
  app.url = window.location.hash.slice(1)
})

const app = new Vue({
  el: '#app',
  data: {
    url: window.location.hash.slice(1)
  },
  components: {
    foo: { template: `<div>foo</div>` },
    bar: { template: `<div>bar</div>` },
  },
  methods: {
    routeTo(route) {
      // 改变hash值
      window.location.hash = route
    }
  }
})
</script>

路由表

上一个小节中不同路由对于组件配置我们时硬编码写在实例中,而通常情况下我们需要一个可配置路由,通过一个路由表来知道那个路径对应那个组件,因此这节我们在此前基础之上,抽取编写一个路由表

<div id="app">
  <component :is="matchedComponent"></component>
  <a @click="routeTo('#foo')" href="#foo">foo</a>
  <a @click="routeTo('#bar')" href="#bar">bar</a>
</div>

<script>
const Foo = { template: `<div>foo</div>` }
const Bar = { template: `<div>bar</div>` }
const NotFound = { template: `<div>not found!</div>` }

// 路由表 
const routeTable = {
  foo: Foo,
  bar: Bar
}

window.addEventListener('hashchange', () => {
  app.url = window.location.hash.slice(1)
})

const app = new Vue({
  el: '#app',
  data: {
    url: window.location.hash.slice(1)
  },
  computed: {
    matchedComponent() {
      // 返回路由表当前路径匹配的组件,如果没匹配到,则返回NotFound组件
      return routeTable[this.url] || NotFound;
    }
  }
})
</script>

动态路由

在应用开发中,我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。

例如有一个 User 组件,对于所有ID不相同的用户,都使用这个组件来渲染(比如说把/user/xiaoshuai和/user/dashuai都映射到相同路由中),那么常用解决方式是使用“动态路径参数”(dynamic segment)

下面我们简单实现下动态路由:

// 利用path-to-regexp库用来分析如何匹配URL中的动态参数部分
<script src="./path-to-regexp.js"></script>

<div id="app"></div>

<script>
const Foo = {
  props: ['id'],
  template: `<div>foo with id: {{ id }}</div>`
}
const Bar = { template: `<div>bar</div>` }
const NotFound = { template: `<div>not found!</div>` }

const routeTable = {
  '/foo/:id': Foo,
  '/bar': Bar
}

// 预编译,遍历原始路由表,通过dynamicSegments来记录路径匹配规则是否包含动态参数
// 最后返回编译过的路由表
const compiledRoutes = [];
Object.keys(routeTable).forEach(key => {
  const dynamicSegments = [];
  const regex = pathToRegxp(key, dynamicSegments);
  const component = routeTable[key];
  // 编译的路由应该包括:1.显示的组件 2. 匹配路径的正则表达式 3. 动态的参数
  compiledRoutes.push({
    component,
    regex,
    dynamicSegments
  })
})

window.addEventListener('hashchange', () => {
  app.url = window.location.hash.slice(1)
})

const app = new Vue({
  el: '#app',
  data: {
    url: window.location.hash.slice(1)
  },
  render(h) {
    const path = '/' + this.url;
    let componentToRender = NotFound;
    let props = {};
    compiledRoutes.some(route => {
      const match = route.regex.exec(path);
      if (match) {
        componentToRender = route.component;
        // 动态参数赋值
        route.dynamicSegments.forEach((segment, index) => {
          props[segment] = match[index + 1];
        })
        return true;
      }
    })
    return h('div', [
      h(componentToRender, { props }),
      h('a', { attrs: { href: '#foo/123' }}, 'foo123'),
      ' | ',
      h('a', { attrs: { href: '#foo/234' }}, 'foo456'),
      ' | ',
      h('a', { attrs: { href: '#bar' }}, 'bar'),
      ' | ',
      h('a', { attrs: { href: '#garbage' }}, 'garbage'),
    ])
  }
})
</script>

以上都是基于个人的理解,欢迎大家提出意见。

自己前端博客:前端学习笔记

参考连接:

前端路由原理解析和实现

vue:路由实现原理

版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 作者: zs10 原文链接:https://juejin.im/post/6925063505460609037

回到顶部