你也許不知道的Vuejs - 自定義路由實現(xiàn)

you-may-not-know-vuejs.png

by yugasun from https://yugasun.com/post/you-may-not-know-vuejs-11.html
本文可全文轉(zhuǎn)載呕缭,但需要保留原作者和出處讲坎。

對于單頁面應(yīng)用,前端路由是必不可少的曹锨,官方也提供了 vue-router 庫 供我們方便的實現(xiàn)扛吞,但是如果你的應(yīng)用非常簡單辜王,就沒有必要引入整個路由庫了,可以通過 Vuejs 動態(tài)渲染的API來實現(xiàn)于样。

我們知道組件可以通過 template 來指定模板抡爹,對于單文件組件掩驱,可以通過 template 標簽指定模板,除此之外冬竟,Vue 還提供了我們一種自定義渲染組件的方式欧穴,那就是 渲染函數(shù) render,具體 render 的使用泵殴,請閱讀官方文檔涮帘。

接下來我們開始實現(xiàn)我們的前端路由了。

簡易實現(xiàn)

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

首先映屋,在 src 目錄先創(chuàng)建 layout/index.vue 文件苟鸯,用來作為頁面的模板同蜻,代碼如下:

<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早处,并修改其代碼湾蔓,使用上面創(chuàng)建的頁面模板包裹:

<template>
  <layout>
      <!-- 原模板內(nèi)容 -->
  </layout>
</template>

<script>
import Layout from '@/layout';

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

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

最后就是我們最重要的步驟了默责,改寫 main.js,根據(jù)頁面 url 動態(tài)切換渲染組件咸包。

1.定義路由映射:

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

2.添加 VueComponent 計算屬性桃序,根據(jù) 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.實現(xiàn)渲染邏輯烂瘫,render 函數(shù)提供了一個參數(shù) createElement媒熊,它是一個生成 VNode 的函數(shù),可以直接將動態(tài)引入組件傳參給它坟比,執(zhí)行渲染芦鳍。

const app = new Vue({
  // ...
  render(h) {
    // 因為組件是以 es module 的方式引入的,
    // 此處必須使用 this.ViewComponent.default 屬性作為參數(shù)
    return h(this.ViewComponent.default);
  }
});

最終實現(xiàn)代碼

history 模式

簡易版本其實并沒有實現(xiàn)前端路由葛账,點擊頁面切換會重新全局刷新柠衅,然后根據(jù) window.location.pathname 來初始化渲染相應(yīng)組件而已。

接下來我們來實現(xiàn)前端路由的 history 模式籍琳。要實現(xiàn)頁面 URL 改變菲宴,但是頁面不刷新,我們就需要用到 history.pushState() 方法趋急,通過此方法喝峦,我們可以動態(tài)的修改頁面 URL,且頁面不會刷新宣谈。該方法有三個參數(shù):一個狀態(tài)對象愈犹,一個標題(現(xiàn)在已被忽略),以及可選的 URL 地址闻丑,執(zhí)行后會觸發(fā) popstate 事件漩怎。

那么我們就不能在像上面一樣直接通過標簽 a 來直接切換頁面了,需要在點擊 a 標簽是嗦嗡,禁用默認事件勋锤,并執(zhí)行 history.pushState() 修改頁面 URL,并更新修改 app.currentRoute侥祭,來改變我們想要的 VueComponent 屬性叁执,好了原理就是這樣茄厘,我們來實現(xiàn)一下。

首先谈宛,編寫通用 router-link 組件次哈,實現(xiàn)上面說的的 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) {
      // 阻止默認跳轉(zhuǎn)事件
      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 屬性作為參數(shù)
    return h(this.ViewComponent.default);
  },
});

好了,我們的 history 模式的路由已經(jīng)修改好了撬槽,點擊頭部的鏈接此改,頁面內(nèi)容改變了,并且頁面沒有刷新侄柔。

但是有個問題共啃,就是當我們點擊瀏覽器 前進/后退 按鈕時,頁面 URL 變化了勋拟,但是頁面內(nèi)容并沒有變化勋磕,這是怎么回事呢?
因為當我們點擊瀏覽器 前進/后退 按鈕時敢靡,app.currentRoute 并沒有發(fā)生改變挂滓,但是它會觸發(fā) popstate 事件,所以我們只要監(jiān)聽 popstate 事件啸胧,然后修改 app.currentRoute 就可以了赶站。

既然需要監(jiān)聽,我們就直接添加代碼吧纺念,在 src/main.js 文件末尾添加如下代碼:

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

這樣我們現(xiàn)在無論是點擊頁面中鏈接切換贝椿,還是點擊瀏覽器 前進/后退 按鈕,我們的頁面都可以根據(jù)路由切換了陷谱。

最終實現(xiàn)代碼

hash 模式

既然實現(xiàn) history 模式烙博,怎么又能少得了 hash 模式 呢?既然你這么問了烟逊,那我還是不辭勞苦的帶著大家實現(xiàn)一遍吧(賣個萌~)渣窜。

什么是 URL hash 呢?來看看 MDN 解釋:

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

也就是說它是頁面 URL中 以 # 開頭的一個字符串標識宪躯。而且當它發(fā)生變化時乔宿,會觸發(fā) hashchange 事件。那么我們可以跟 history 模式 一樣對其進行監(jiān)聽就行了访雪,對于 history 模式详瑞,
這里需要做的修改無非是 src/routes.js 的路由映射如下:

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

src/layout/index.vue 中的鏈接都添加 # 前綴掂林,然后在 src/main.js 中監(jiān)聽 hashchange 事件,當然還需要將 window.location.hash 賦值給 app.currentRoute

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

最后還有個問題坝橡,就是單個面初始化的時候泻帮,window.location.hash 值為空,這樣就會找不到路由映射驳庭。所以當頁面初始化的時候刑顺,需要添加判斷,如果 window.location.hash 為空饲常,則默認修改為 #/,這樣就全部完成了狼讨。

最終實現(xiàn)代碼

不同模式切換版本

實際開發(fā)中贝淤,我們會根據(jù)不同項目需求,使用不同的路由方式政供,這里就需要我們添加一個 mode 參數(shù)播聪,來實現(xiàn)路由方式切換,這里就不做講解了布隔,感興趣的讀者离陶,可以自己嘗試實現(xiàn)下。

總結(jié)

實際上衅檀,一個完整的路由庫招刨,遠遠不止我們上面演示的代碼那么簡單,還需要考慮很多問題哀军,但是如果你的項目非常簡單沉眶,不需要很復(fù)雜的路由機制,自己實現(xiàn)一遍還是可以的杉适,畢竟 vue-router.min.js 引入進來代碼體積就會增加 26kb谎倔,具體如何取舍,還是視需求而定猿推。

盡信書片习,不如無書,當面對 問題/需求 時蹬叭,多點自主的思考和實踐藕咏,比直接接受使用要有用的多。

專題目錄

You-May-Not-Know-Vuejs

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末具垫,一起剝皮案震驚了整個濱河市侈离,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌筝蚕,老刑警劉巖卦碾,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铺坞,死亡現(xiàn)場離奇詭異,居然都是意外死亡洲胖,警方通過查閱死者的電腦和手機济榨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绿映,“玉大人擒滑,你說我怎么就攤上這事〔嫦遥” “怎么了丐一?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長淹冰。 經(jīng)常有香客問我库车,道長,這世上最難降的妖魔是什么樱拴? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任柠衍,我火速辦了婚禮,結(jié)果婚禮上晶乔,老公的妹妹穿的比我還像新娘珍坊。我一直安慰自己,他們只是感情好正罢,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布阵漏。 她就那樣靜靜地躺著,像睡著了一般腺怯。 火紅的嫁衣襯著肌膚如雪袱饭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天呛占,我揣著相機與錄音虑乖,去河邊找鬼。 笑死晾虑,一個胖子當著我的面吹牛疹味,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播帜篇,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼糙捺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了笙隙?” 一聲冷哼從身側(cè)響起洪灯,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎竟痰,沒想到半個月后签钩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掏呼,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年铅檩,在試婚紗的時候發(fā)現(xiàn)自己被綠了憎夷。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡昧旨,死狀恐怖拾给,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情兔沃,我是刑警寧澤蒋得,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站粘拾,受9級特大地震影響窄锅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缰雇,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望追驴。 院中可真熱鬧械哟,春花似錦、人聲如沸殿雪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丙曙。三九已至爸业,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間亏镰,已是汗流浹背扯旷。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留索抓,地道東北人钧忽。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像逼肯,于是被迫代替她去往敵國和親耸黑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容