你也許不知道的Vuejs - 自定義路由實現

by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但須要保留原做者和出處。javascript

對於單頁面應用,前端路由是必不可少的,官方也提供了 vue-router 庫 供咱們方便的實現,可是若是你的應用很是簡單,就沒有必要引入整個路由庫了,能夠經過 Vuejs 動態渲染的API來實現。css

咱們知道組件能夠經過 template 來指定模板,對於單文件組件,能夠經過 template 標籤指定模板,除此以外,Vue 還提供了咱們一種自定義渲染組件的方式,那就是 渲染函數 render,具體 render 的使用,請閱讀官方文檔。html

接下來咱們開始實現咱們的前端路由了。前端

簡易實現

咱們先運行 vue init webpack vue-router-demo 命令來初始化咱們的項目(注意初始化的時候,不要選擇使用 vue-router)。vue

首先,在 src 目錄先建立 layout/index.vue 文件,用來做爲頁面的模板,代碼以下:java

<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,並修改其代碼,使用上面建立的頁面模板包裹:webpack

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

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

固然還須要添加一個 404頁面,用來充當當用戶輸入不存在的路由時的界面。git

最後就是咱們最重要的步驟了,改寫 main.js,根據頁面 url 動態切換渲染組件。github

1.定義路由映射:web

// 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

相關文章
相關標籤/搜索