你也许不知道的Vuejs - Vuejs 自定义路由实现

by yugasun from https://yugasun.com/post/you-may-not-know-vuejs-11.html 本文可全文转载,但须要保留原做者和出处。html

对于单页面应用,前端路由是必不可少的,官方也提供了 vue-router 库 供咱们方便的实现,可是若是你的应用很是简单,就没有必要引入整个路由库了,能够经过 Vuejs 动态渲染的API来实现。前端

咱们知道组件能够经过 template 来指定模板,对于单文件组件,能够经过 template 标签指定模板,除此以外,Vue 还提供了咱们一种自定义渲染组件的方式,那就是 渲染函数 render,具体 render 的使用,请阅读官方文档。vue

接下来咱们开始实现咱们的前端路由了。webpack

简易实现

咱们先运行 vue init webpack vue-router-demo 命令来初始化咱们的项目(注意初始化的时候,不要选择使用 vue-router)。git

首先,在 src 目录先建立 layout/index.vue 文件,用来做为页面的模板,代码以下:github

<template>
  <div class="container">
    <ul>
      <li><a :class="{active: $root.currentRoute === '/'}" href="/">Home</a></li>
      <li><a :class="{active: $root.currentRoute === '/hello'}" href="/hello">HelloWord</a></li>
    </ul>

    <slot></slot>
  </div>
</template>
<script> export default { name: 'Layout', }; </script>
<style scoped> .container { max-width: 600px; margin: 0 auto; padding: 15px 30px; background: #f9f7f5; } a.active { color: #42b983; } </style>

而后,将 components/HelloWorld.vue 移动到 src/pages,并修改其代码,使用上面建立的页面模板包裹:web

<template>
  <layout>
      <!-- 原模板内容 -->
  </layout>
</template>

<script> import Layout from '@/layout'; export default { name: 'HelloWorld', components: { Layout, }, // ... }; </script>
<!-- ... -->

固然还须要添加一个 404页面,用来充当当用户输入不存在的路由时的界面。vue-router

最后就是咱们最重要的步骤了,改写 main.js,根据页面 url 动态切换渲染组件。浏览器

1.定义路由映射:app

// url -> Vue Component
const routes = {
  '/': 'Home',
  '/hello': 'HelloWorld',
};

2.添加 VueComponent 计算属性,根据 window.location.pathname 来引入所须要组件。

const app = new Vue({
  el: '#app',
  data() {
    return {
      // 当前路由
      currentRoute: window.location.pathname,
    };
  },
  computed: {
    ViewComponent() {
      const currentView = routes[this.currentRoute];
      /* eslint-disable */
      return (
        currentView
          ? require('./pages/' + currentView + '.vue')
          : require('./pages/404.vue')
      );
    },
  },
});

3.实现渲染逻辑,render 函数提供了一个参数 createElement,它是一个生成 VNode 的函数,能够直接将动态引入组件传参给它,执行渲染。

const app = new Vue({
  // ...
  render(h) {
    // 由于组件是以 es module 的方式引入的,
    // 此处必须使用 this.ViewComponent.default 属性做为参数
    return h(this.ViewComponent.default);
  }
});

最终实现代码

history 模式

简易版本其实并无实现前端路由,点击页面切换会从新全局刷新,而后根据 window.location.pathname 来初始化渲染相应组件而已。

接下来咱们来实现前端路由的 history 模式。要实现页面 URL 改变,可是页面不刷新,咱们就须要用到 history.pushState() 方法,经过此方法,咱们能够动态的修改页面 URL,且页面不会刷新。该方法有三个参数:一个状态对象,一个标题(如今已被忽略),以及可选的 URL 地址,执行后会触发 popstate 事件。

那么咱们就不能在像上面同样直接经过标签 a 来直接切换页面了,须要在点击 a 标签是,禁用默认事件,并执行 history.pushState() 修改页面 URL,并更新修改 app.currentRoute,来改变咱们想要的 VueComponent 属性,好了原理就是这样,咱们来实现一下。

首先,编写通用 router-link 组件,实现上面说的的 a 标签点击逻辑,添加 components/router-link.vue,代码以下:

<template>
  <a :href="href" :class="{active: isActive}" @click="go" >
    <slot></slot>
  </a>
</template>
<script> import routes from '@/routes'; export default { name: 'router-link', props: { href: { type: String, required: true, }, }, computed: { isActive() { return this.href === this.$root.currentRoute; }, }, methods: { go(e) { // 阻止默认跳转事件 e.preventDefault(); // 修改父级当前路由值 this.$root.currentRoute = this.href; window.history.pushState( null, routes[this.href], this.href, ); }, }, }; </script>

对于 src/main.js 文件,其实不须要作什么修改,只须要将 routes 对象修改成模块引入便可。以下:

import Vue from 'vue';

// 这里将 routes 对象修改成模块引入方式
import routes from './routes';

Vue.config.productionTip = false;

/* eslint-disable no-new */
const app = new Vue({
  el: '#app',
  data() {
    return {
      currentRoute: window.location.pathname,
    };
  },
  computed: {
    ViewComponent() {
      const currentView = routes[this.currentRoute];
      /* eslint-disable */
      return (
        currentView
          ? require('./pages/' + currentView + '.vue')
          : require('./pages/404.vue')
      );
    },
  },
  render(h) {
    // 由于组件是以 es module 的方式引入的,
    // 此处必须使用 this.ViewComponent.default 属性做为参数
    return h(this.ViewComponent.default);
  },
});

好了,咱们的 history 模式的路由已经修改好了,点击头部的连接,页面内容改变了,而且页面没有刷新。

可是有个问题,就是当咱们点击浏览器 前进/后退 按钮时,页面 URL 变化了,可是页面内容并无变化,这是怎么回事呢? 由于当咱们点击浏览器 前进/后退 按钮时,app.currentRoute 并无发生改变,可是它会触发 popstate 事件,因此咱们只要监听 popstate 事件,而后修改 app.currentRoute 就能够了。

既然须要监听,咱们就直接添加代码吧,在 src/main.js 文件末尾添加以下代码:

window.addEventListener('popstate', () => {
  app.currentRoute = window.location.pathname;
});

这样咱们如今不管是点击页面中连接切换,仍是点击浏览器 前进/后退 按钮,咱们的页面均可以根据路由切换了。

最终实现代码

hash 模式

既然实现 history 模式,怎么又能少得了 hash 模式 呢?既然你这么问了,那我仍是不辞劳苦的带着你们实现一遍吧(卖个萌~)。

什么是 URL hash 呢?来看看 MDN 解释:

Is a DOMString containing a '#' followed by the fragment identifier of the URL.

也就是说它是页面 URL中 以 # 开头的一个字符串标识。并且当它发生变化时,会触发 hashchange 事件。那么咱们能够跟 history 模式 同样对其进行监听就好了,对于 history 模式, 这里须要作的修改无非是 src/routes.js 的路由映射以下:

export default {
  '#/': 'Home',
  '#/hello': 'HelloWorld',
};

src/layout/index.vue 中的连接都添加 # 前缀,而后在 src/main.js 中监听 hashchange 事件,固然还须要将 window.location.hash 赋值给 app.currentRoute

window.addEventListener('hashchange', () => {
  app.currentRoute = window.location.hash;
});

最后还有个问题,就是单个面初始化的时候,window.location.hash 值为空,这样就会找不到路由映射。因此当页面初始化的时候,须要添加判断,若是 window.location.hash 为空,则默认修改成 #/,这样就所有完成了。

最终实现代码

不一样模式切换版本

实际开发中,咱们会根据不一样项目需求,使用不一样的路由方式,这里就须要咱们添加一个 mode 参数,来实现路由方式切换,这里就不作讲解了,感兴趣的读者,能够本身尝试实现下。

总结

实际上,一个完整的路由库,远远不止咱们上面演示的代码那么简单,还须要考虑不少问题,可是若是你的项目很是简单,不须要很复杂的路由机制,本身实现一遍仍是能够的,毕竟 vue-router.min.js 引入进来代码体积就会增长 26kb,具体如何取舍,仍是视需求而定。

尽信书,不如无书,当面对 问题/需求 时,多点自主的思考和实践,比直接接受使用要有用的多。

专题目录

You-May-Not-Know-Vuejs

相关文章
相关标签/搜索