前端面試中,會問到很是多的知識點。框架,幾乎是必問的問題之一。Vue 做爲目前最流行的 SPA 框架之一,是面試過程當中的重頭戲。Vue Router 做爲 Vue 生態中極其重要的角色,也是咱們必須掌握的一項技能。javascript
這篇文章將介紹 Vue Router 的使用,而且本身動手實現一個簡易版的 Vue Router。css
首先使用 vue cli 建立一個 Vue 項目來回顧一下 vue router 的使用。html
全局安裝 vue cli。前端
npm i -g @vue/cli
複製代碼
安裝完成後檢查版本是否正常。vue
vue --version
複製代碼
而後建立一個演示項目。java
vue create vue-router-demo
複製代碼
首先使用自定義選擇。Manually select features。node
vue cli 會詢問一些問題。只須要選擇三項 Babel, Router, Linter。webpack
這樣 vue cli 會幫咱們建立 vue router 的基本代碼結構。nginx
進入項目並啓動項目。web
npm run serve
複製代碼
而後就能夠在瀏覽器中看到路由的效果了。
在 vue 中使用 vue router 的步驟大體有以下幾步。
建立路由對應的頁面。
默認在 views 文件夾中。
使用 Vue.use(VueRouter)來註冊路由插件。Vue.use 方法是專門用來註冊插件的,若是傳入的是函數,會直接調用。若是傳入的是對象,會調用對象的 install 方法。
默認在 router/index.js 中。
首先定義一套路由規則,路由規則是一個數組,數組中包含不少對象,每個對象都是一個規則。對象上面會有 path 和 component 等屬性,path 表明着路徑,compoent 表明着渲染的組件。當瀏覽器中 path 發生變化時,會渲染對應的 component 到頁面中。
經過 new VueRouter 的方式建立對象,VueRouter 的構造函數是一個對象。要把這個對象的 routes 屬性設置爲剛剛定義的路由規則。
默認在 router/index.js 中。
在 new Vue 時,配置對象中的 router 選項設置爲上面建立的路由對象。
在 Vue 實例指定的 el 選項對應的元素中,使用 router-view 標籤建立路由組件的佔位。路由組件每次都會渲染到這個位置。
使用 router-link 建立連接,經過 router-link 來改變路由。
當 Vue 實例開啓 router 選項後,實例對象會多出兩個屬性,分別是 $route 和 $router。
$route 是當前的路由規則對象,裏面存儲了路徑、參數等信息。
$router 是路由實例對象,裏面存儲了不少路由的方法,好比 push、replace、go 等。還存儲了路由的信息,好比 mode 和 currentRoute。
假設有一個用戶詳情頁面。咱們不會給每個用戶都建立一個詳情頁面,由於這個頁面是通用的,變化的只是用戶的 id。
首先添加一個路由。
:id 前面的路徑是固定的,:id 自己的意思就是接收一個 id 參數。
component 返回的是一個函數,這就是路由懶加載的寫法。
也就是當這個路由被觸發時,纔會渲染這個組件。當這個路由沒有被觸發時,不會渲染組件。能夠提升性能。
// ... other code
const routes = [ // ... other code { path: "/detail/:id", name: "Detail", component: () => import("../views/Detail.vue"), }, ]; 複製代碼
有了路由以後,再建立一個用戶頁面。
<template>
<div>當前用戶ID:{{ $route.params.id }}</div> </template> <script> export default { name: "Detail", }; </script> 複製代碼
這就是第一種獲取動態路由參數的方式,經過路由規則,獲取參數。
可是這種方式有一個缺點,就是強制依賴 $route 才能正常工做。
可使用另外一種方式來下降這種依賴。
在路由規則中開啓 props 屬性。
// ... other code
const routes = [ // ... other code { path: "/detail/:id", name: "Detail", props: true, component: () => import("../views/Detail.vue"), }, ]; 複製代碼
props 屬性的做用是將路由中的參數以 props 的形式傳入到組件中,這樣在組件內就能夠經過 props 獲取到參數。
<template>
<div>當前用戶ID:{{ id }}</div> </template> <script> export default { name: "Detail", props: ["id"], }; </script> 複製代碼
這樣 Detail 這個組件就不是必須在路由中才可使用,只要傳遞一個 id 屬性,它就能夠被應用到任何位置。
因此更加推薦使用 props 的方式傳遞路由參數。
當多個路由組件具備相同的內容,能夠把多個路由組件相同的內容提取到一個公共的組件中。
假設首頁和詳情頁具備相同的頭部和尾部。能夠提取一個 layout 組件,把頭部和尾部抽取到 layout 組件中,並在發生變化的位置放置一個 router view。當訪問對應的路由時,會把路由組件和 layout 組件的內容合併輸出。
假設還有一個登陸頁面,它不是須要 layout 的,因此它也不須要嵌套路由。
編寫 layout 組件。
<template>
<div> <div> <header>header</header> </div> <div> <router-view /> </div> <div> <footer>footer</footer> </div> </div> </template> <script> export default { name: "login", }; </script> <style> header { width: 100%; background: #65b687; color: #39495c; } footer { width: 100%; background: #39495c; color: #65b687; } </style> 複製代碼
建立 Login.vue 組件。
<template>
<div>登錄頁</div> </template> <script> export default { name: "login", }; </script> 複製代碼
修改 app.vue 中 template 代碼塊中的內容。
<template>
<div id="app"> <router-view /> </div> </template> 複製代碼
修改 routes 配置。
const routes = [
{ path: "/login", name: "login", component: Login, }, { path: "/", component: Layout, children: [ { name: "home", path: "", component: Home, }, { name: "detail", path: "detail:id", props: true, component: () => import("../views/Detail.vue"), }, ], }, ]; 複製代碼
這樣當訪問http://localhost:8080/login時,會正常進入Login組件。
訪問http://localhost:8080/時,會首先加載/對應的Layout組件,而後再加載Home組件,並把Layout組件和Home組件的內容進行合併。
訪問http://localhost:8080/detail/id時,也會和Home加載方式同樣,先加載Layout,再加載Detail,並把id傳遞進去。最後把兩個組件的內容合併。
除了使用 router-link 進行導航之外,咱們還可使用 js 代碼的方式進行導航。
這種需求很是常見,好比點擊一個按鈕,進行邏輯判斷後再進行導航。
經常使用的編程式導航 API 有 4 個。分別是 $router.push、$router.replace、$router.back 和 $router.go
改造一下上面的三個頁面,來體驗一下這 4 個 API。
登錄頁經過點擊登錄按鈕跳轉到首頁。
<template>
<div> <div>登錄頁</div> <button @click="push">登錄</button> </div> </template> <script> export default { name: "login", methods: { push() { this.$router.push("/"); // this.$router.push({ name: 'home' }) }, }, }; </script> <style> button { background: #39495c; color: #65b687; border-radius: 8px; padding: 5px 10px; border: none; outline: none; } </style> 複製代碼
首頁能夠跳轉到用戶詳情頁,也能夠退出,退出的話跳轉到登錄頁,而且在瀏覽器的瀏覽歷史中不保存當前頁。
<template>
<div class="home"> <div>Home Page.</div> <button @click="goToDetail">查看用戶8的資料</button> <button @click="exit">退出</button> </div> </template> <script> export default { name: "Home", methods: { goToDetail() { this.$router.push("/detail/8"); // this.$router.push({ name: 'detail', params: { id: 8 } }) }, exit() { // this.$router.replace('/login') this.$router.replace({ name: "login" }); }, }, }; </script> 複製代碼
用戶詳情頁中能夠回退到上一頁,也能夠回退兩頁。
<template>
<div> <div>當前用戶ID:{{ id }}</div> <button @click="back">返回</button> <button @click="backTwo">回退兩頁</button> </div> </template> <script> export default { name: "Detail", props: ["id"], methods: { back() { this.$router.back(); }, backTwo() { this.$router.go(-2); }, }, }; </script> 複製代碼
其中 push 方法和 replace 方法的用法基本上是一致的,均可以經過傳遞一個字符串或者傳遞一個對象來實現頁面導航。若是傳遞字符串的話,就表示頁面的路徑。傳遞對象的話,會根據對象的 name 屬性去尋找對應的頁面組件。若是須要傳遞參數,能夠拼接字符串,也能夠在對象中設置 params 屬性。二者不一樣之處在於 replace 方法不會在瀏覽器中記錄當前頁面的瀏覽歷史,而 push 方法會記錄。
back 方法是回到上一頁,它的用法最簡單,不須要傳遞參數。
go 方法能夠傳遞一個 number 類型的參數,表示是前進仍是後退。負數表示後退,正數表示前進,0 的話刷新當前頁面。
Vue Router 中的路由模式有兩種,分別是 hash 模式和 history 模式,hash 模式會在導航欄地址中具備一個井號(#),history 模式則沒有。
兩種模式都是由客戶端來處理的,使用 JavaScript 來監聽路由的變化,根據不一樣的 URL 渲染不一樣的內容。若是須要服務端內容的話,使用 Ajax 來獲取。
從美觀上來看,history 模式更加美觀。
hash 模式的 URL。會附帶一個井號(#),若是傳遞參數的話,還須要問號(?)。
http://localhost:8080/#/user?id=15753140
複製代碼
history 模式的連接。
http://localhost:8080/user/15753140
複製代碼
可是 history 不能夠直接使用,須要服務端配置支持。
Hash 模式是基於錨點以及 onhashchange 事件。
History 模式是基於 HTML5 中的 History API。history 對象具備 pushState 和 replaceState 兩個方法。可是須要注意 pushState 方法須要 IE10 之後才能夠支持。在 IE10 以前的瀏覽器,只能使用 Hash 模式。
history 對象還有一個 push 方法,能夠改變導航欄的地址,並向服務器發送請求。pushState 方法能夠只改變導航欄地址,而不向服務器發送請求。
History 須要服務器的支持。
緣由是單頁面應用中,只有一個 index.html。而在單頁面應用正常經過點擊進入 http://localhost:8080/login 不會有問題。可是當刷新瀏覽器時,就會請求服務器,而服務器上不存在這個 URL 對應的資源,就會返回 404。
因此在服務器上應該配置除了靜態資源之外的全部請求都返回 index.html。
下面演示一下頁面匹配不到的效果。
在 views 目錄下建立 404.vue。
<template>
<div class="about"> <h1>404</h1> </div> </template> 複製代碼
在 routes 中添加 404 的路由。
const routes = [
// other code { path: "*", name: "404", component: () => import("../views/404.vue"), }, ]; 複製代碼
在 Home.vue 中添加一個不存在的連接。
<router-link to="/video">video</router-link>
複製代碼
而後啓動服務器,進入首頁,點擊 video 連接,就會跳轉到 404 頁面。
這是一個咱們預期想要的效果。在 vue cli 默認的服務器中,已經幫咱們配置好了。可是在咱們實際部署的時候,仍然須要本身去配置服務器。
首先使用 nodejs 開發一個服務器。
建立一個 server 文件夾,並初始化項目。
npm init -y
複製代碼
安裝項目的依賴,這裏使用 express 和 connect-history-api-fallback。
express 是一個 nodejs 著名的 web 開發服務器框架。
connect-history-api-fallback 是一個處理 history 模式的模塊。
npm i express connect-history-api-fallback
複製代碼
建立並編寫 server.js 文件。
const path = require("path");
// 處理 history 模式的模塊 const history = require("connect-history-api-fallback"); const express = require("express"); const app = express(); // 註冊處理 history 模式的中間件 app.use(history()); // 註冊處理靜態資源的中間件 app.use(express.static(path.join(__dirname, "./web"))); app.listen(4000, () => { console.log(` App running at: - Local: http://localhost:4000/ `); }); 複製代碼
這裏把 server 項目下根目錄的 web 文件夾設置爲網站的根路徑。
當啓動 server.js 後,請求http://localhost:4000/的URL都會去web文件夾下找到相應的資源。
如今打包原來的 vue 項目。
回到 vue 項目中,運行打包命令。
npm run build
複製代碼
能夠獲得 dist 文件夾。
將 dist 目錄中的全部內容複製到 server 項目的 web 目錄中,就完成了項目的部署。
接下來運行 server.js。
node server.js
複製代碼
打開瀏覽器,進入 detail 頁面(http://localhost:4000/detail/8)。刷新瀏覽器,一切正常。
若是不處理 history,就會出現問題。
嘗試把 app.use(history())
註釋掉,從新啓動服務器。
一樣進入 detail 頁面,刷新瀏覽器,就會進入 express 默認的 404 頁面。緣由就是刷新瀏覽器,會請求服務器。服務器在 web 目錄下找不到 detail/8 資源。若是開啓了 history 處理,服務器找不到 detail/8,就會返回 index.html,客戶端會根據當前路徑渲染組件。
首先安裝 nginx。
能夠在 nginx 官網下載 nginx 的壓縮包。
http://nginx.org/en/download.html
把壓縮包解壓到不附帶中文的目錄下。
或者藉助某些工具安裝,好比 brew。
brew install nginx
複製代碼
nginx 的命令比較簡單,經常使用的命令以下。
啓動
nginx
複製代碼
重啓
nginx -s reload
複製代碼
中止
nginx -s stop
複製代碼
壓縮包的方式安裝,nginx 的默認端口是 80,若是 80 未被佔用,會正常啓動。啓動後在瀏覽器訪問http://localhost便可訪問。
brew 方式安裝的 nginx 默認端口是 8080。
把 vue 項目 dist 文件夾中的內容拷貝到 nginx 文件夾中的 html 文件夾中。html 文件夾就是 nginx 的默認文件夾。
部署成功後,在瀏覽器中訪問項目,發現會存在一樣的刷新 404 問題。
這時就須要在 nginx 的配置文件中添加對應的配置。
nginx 的默認配置在 conf/nginx.conf 中。
在 nginx.conf 中找到監聽 80 的那個 server 模塊,在從中找到 location /的位置。
添加 try_files 配置。
location / {
root html;
index index.html index.htm;
# $uri 是 nginx 的變量,就是當前此次請求的路徑
# try files 會嘗試在這個路徑下尋找資源,若是找不到,會繼續朝下一個尋找
# $uri/ 的意思是在路徑目錄下尋找 index.html 或 index.htm
# 最後都找不到的話,返回 index.html
try_files $uri $uri/ /index.html;
}
複製代碼
修改完配置文件後,nginx 須要重啓。
nginx -s reload
複製代碼
重啓後在瀏覽器中操做,一切正常。
因爲 history 和 hash 模式的實現很像,這裏直接使用 history 模式進行模擬。
如今再次回顧一下 vue router 的工做原理。
vue router 是前端路由,當路徑切換時,在瀏覽器端判斷當前路徑並加載當前路徑對應的組件。
hash 模式:
history 模式:
經過觀察 vue router 的使用,能夠快速推斷出 vue router 是如何實現的。
下面是一個簡單的使用流程。
// 註冊插件
Vue.use(VueRouter); // 建立路由對象 const router = new VueRouter({ routes: [{ name: "home", path: "/", component: homeComponent }], }); // 建立 Vue 實例,註冊 router 對象 new Vue({ router, render: (h) => h(App), }).$mount("#app"); 複製代碼
首先是執行 Vue.use 註冊 VueRouter。
Vue.use 方法是用於註冊插件的,Vue 的強大,得益於它的插件機制。像 VueRouter、Vuex 和一些組件,都是使用插件機制實現的。
Vue.use 方法能夠接受 1 個函數或者 1 個對象,若是是函數,則直接調用該函數,若是是對象,則調用對象上的 install 方法。這裏的 VueRouter 是一個對象。
接下來建立了一個 router 實例,那麼 VueRouter 應該是一個構造函數或者是一個類。
結合上面的分析,能夠得知,VueRouter 是一個具備 install 方法的類。
VueRouter 的構造函數是一個對象,構造參數對象會有一個 routes 屬性,記錄了路由的配置信息。
最後在建立 Vue 實例的構造參數對象中傳入了 router 對象。
能夠經過 UML 類圖來描述 VueRouter 這個類。
UML 類圖包含 3 個部分。
最上面是類的名字,第二部分是類的實例屬性,第三部分是類的方法,其中加號(+)表示原型方法、下劃線(_)表示靜態方法。
使用 vue cli 建立一個新的項目,配置選項中選擇 babel、vue router、eslint,以便用於咱們測試。
當使用 Vue.use()時,會首先調用 install,因此先實現 install。
首先要分析,install 中要實現哪幾件事情。
在 src 目錄下建立 vue-router 目錄,並在其中建立 index.js 文件。
let _Vue = null;
export default class VueRouter { static install(Vue) { // 1. 判斷當前插件是否已安裝 if (VueRouter.install.installed) { return; } VueRouter.install.installed = true; // 2. 把 Vue 構造函數存儲到全局變量中 _Vue = Vue; // 3. 把建立 Vue 實例時傳入的 router 對象注入到全部 Vue 實例上 // 混入 _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; } }, }); } } 複製代碼
第一步比較簡單,記錄一個是否被插件,相比於全局變量,更好的方式就是在插件自己的 install 方法上添加一個 installed 屬性。若是已安裝,直接返回。未安裝,把 installed 設置爲 true,繼續執行邏輯。
第二步很是簡單,只須要給全局的_Vue 賦值就能夠了。
第三步比較難,由於在這裏咱們並不知道何時會調用 new Vue,因此也獲取不到構造參數中的 router 對象。這時能夠藉助混入來解決這個問題。在 mixin 方法中傳入的對象具備 beforeCreate 方法,這個是 new Vue 時的鉤子函數,該函數中的 this 指向的就是 Vue 實例,因此在這裏能夠將 VueRouter 實例注入到全部的 Vue 實例上。因爲每一個組件也是一個 Vue 實例,因此還須要區分是 Vue 實例仍是組件,否則原型擴展的邏輯會被執行不少次。具體經過 this.$options 是否具有 router 屬性來判斷,由於只有 Vue 實例纔會具備 router 屬性,組件是沒有的。
接下來實現構造函數,構造函數的邏輯比較簡單。
建立了三個實例屬性。options 用來存儲構造參數;routerMap 就是一個鍵值對對象,屬性名就是路由地址,屬性值就是組件;data 是一個響應式對象,具備一個 current 屬性,用於記錄當前的路由地址。能夠經過_Vue.observable 來建立響應式對象。
export default class VueRouter {
// other code constructor(options) { this.options = options; this.routeMap = {}; this.data = _Vue.observable({ current: "/", }); } } 複製代碼
該函數的做用是將構造函數參數 options 中的 routes 屬性轉換爲鍵值對的形式存儲到 routeMap 上。
export default class VueRouter {
// other code initRouteMap() { this.options.routes.forEach((route) => { this.routeMap[route.path] = route.component; }); } } 複製代碼
接下來實現 initComponents,這個方法主要是註冊 router-link 和 router-view 這兩個組件。
initComponents 方法接收 1 個 Vue 構造方法做爲參數,傳入參數的目的是爲了減小方法和外部的依賴。
router-link 組件會接收一個字符串類型的參數 to,就是一個連接。router-link 自己會轉換成 a 標籤,而 router-link 的內容也會被渲染到 a 標籤內。
export default class VueRouter {
// other code initComponents(Vue) { Vue.component("router-link", { props: { to: String, }, template: `<a :href="to"><slot></slot></a>`, }); } } 複製代碼
建立 init 函數,這個函數將 initRouteMap 和 initComponents 包裝起來,方便使用。
而後在建立 Vue 實例時調用 init 方法,建立 router-link 組件。
export default class VueRouter {
// other code static install(Vue) { if (VueRouter.install.installed) { return; } VueRouter.install.installed = true; _Vue = Vue; _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; // 在這裏調用 init this.$options.router.init(); } }, }); } init() { initRouteMap(); initComponents(); } } 複製代碼
如今就能夠去測試了。
將 src/router/index.js 的 vue-router 替換爲咱們本身寫的 vue router。
// import VueRouter from 'vue-router'
import VueRouter from "../../vue-router/index"; 複製代碼
啓動項目。
npm run serve
複製代碼
打開瀏覽器,會發現頁面上一片空白,可是控制檯會獲得兩個錯誤。
第一個錯誤是:
vue.runtime.esm.js?2b0e:619 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.
複製代碼
這個錯誤的意思是目前使用的是運行時版本的 Vue,模板編譯器不可用。可使用預編譯將模板編譯成渲染函數,或者使用編譯版本的 Vue。
Vue 的構建版本:
運行時版:不支持 template 模板,須要打包的時候提早編譯。
完整版:包含運行時和編譯器,體積比運行時版本大 10k 左右,程序運行的時候把模板轉換成 render 函數。
第二個錯誤是 router-view 組件未定義,由於如今尚未處理 router-view,能夠忽略。
Vue Cli 建立的項目,默認使用運行時版本的 Vue,由於它的效率更高。
若是要修改 Vue Cli 項目的配置,須要在項目根目錄下建立 vue.config.js 文件,這個文件使用 CommonJS 規範導出一個模塊。
將 runtimeCompiler 設置爲 true 就可使用完整版 Vue,默認狀況下這個選項是 false。
module.exports = {
runtimeCompiler: true, }; 複製代碼
而後從新啓動項目,以前碰到的第一個問題就獲得瞭解決。
可是完整版本的 Vue 體積會大 10k,並且是運行時編譯,消耗性能,不建議使用。
運行時版本的 Vue 不包含編譯器,因此也不支持 template 選項。而編譯器的做用就是將 template 選項轉換爲 render 函數。
咱們在編寫.vue 文件時,在不開啓 runtimeCompiler 時也不會編寫 render 函數。這時由於 Vue Cli 中配置的 webpack 會在代碼編譯打包階段將 vue 文件中的 template 轉換爲 render 函數,也就是預編譯。而咱們編寫的 js 文件,是沒有進行這種預編譯的。因此要在運行時版本的 Vue 中須要使用 render 函數。
首先刪除掉 vue.config.js。
修改 initComponents 函數。
export default class VueRouter {
// other code initComponents(Vue) { Vue.component("router-link", { props: { to: String, }, // template: `<a :href="to"><slot></slot></a>` render(h) { return h( "a", { attrs: { href: this.to, }, }, [this.$slots.default] ); }, }); } } 複製代碼
render 函數接收一個 h 函數,h 函數的做用是建立虛擬 DOM,最終 render 將返回虛擬 DOM。
h 函數的用法有不少種,具體可參考官方文檔:https://cn.vuejs.org/v2/guide/render-function.html
從新啓動項目,符合預期。
router-view 組件相似於 slot 組件,提供一個佔位符的做用。根據不一樣的路由地址,獲取到不一樣的路由組件,並渲染到 router-view 的位置。
export default class VueRouter {
// other code initComponents(Vue) { // other code const self = this; Vue.component("router-view", { render(h) { const component = self.routeMap[self.data.current]; return h(component); }, }); } } 複製代碼
這樣就完成了 router-view 組件。
可是如今去嘗試點擊超連接,發現並不能正常跳轉。緣由是由於 a 標籤會默認請求服務器,致使頁面刷新。
因此須要阻止 a 標籤默認請求服務器的行爲,並使用 histor.pushState 方法改變導航欄的 URL,改變的 URL 要保存到 this.data.current 中。由於 this.data 是響應式數據。
修改 router-link 組件的邏輯。
export default class VueRouter {
// other code initComponents(Vue) { // other code Vue.component("router-link", { props: { to: String, }, render(h) { return h( "a", { attrs: { href: this.to, }, on: { click: this.clickHandler, }, }, [this.$slots.default] ); }, methods: { clickHandler(e) { history.pushState({}, "", this.to); this.$router.data.current = this.to; e.preventDefault(); }, }, }); } } 複製代碼
再次回到項目中,運行項目。點擊 a 標籤,就能夠正常刷新頁面內容了。
雖然上面已經實現了全部的功能,可是還存在一個小問題。
點擊瀏覽器左上角的前進、後退按鈕時,只是修改了地址欄的 URL,頁面並無隨之發生改變。
解決這個問題也很簡單。
實現思路是監聽 popstate 方法,並在其中將 this.data.current 的值設置爲當前導航欄的 URL。因爲 this.data 是響應式的數據,因此當 this.data 發生變化時,全部用到 this.data 的組件都會被從新渲染。
export default class VueRouter {
// other code init() { // other code this.initEvent(); } initEvent(Vue) { window.addEventListener("popstate", () => { this.data.current = window.location.pathname; }); } } 複製代碼
這樣就解決了導航欄前進後退不刷新組件的小問題。
至此,history 模式的 vue router 簡單實現已經完成。
附所有源碼:
let _Vue = null;
export default class VueRouter { static install(Vue) { // 1. 判斷當前插件是否已經被安裝 if (VueRouter.install.installed) { return; } VueRouter.install.installed = true; // 2. 把 Vue 構造函數記錄到全局變量 _Vue = Vue; // 3. 把建立的 Vue 實例時所傳入的 router 對象注入到 Vue 實例上 // 混入 _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router; this.$options.router.init(); } }, }); } constructor(options) { this.options = options; this.routeMap = {}; this.data = _Vue.observable({ current: "/", }); } init() { this.initRouterMap(); this.initComponents(_Vue); this.initEvent(); } initRouterMap() { // 遍歷全部的路由規則,把路由規則解析成鍵值對的形式 存儲到 routerMap 中 this.options.routes.forEach((route) => { this.routeMap[route.path] = route.component; }); } initComponents(Vue) { Vue.component("router-link", { props: { to: String, }, // template: ` // <a :href="to"> // <slot></slot> // </a> // `, render(h) { return h( "a", { attrs: { href: this.to, }, on: { click: this.clickHandler, }, }, [this.$slots.default] ); }, methods: { clickHandler(e) { history.pushState({}, "", this.to); this.$router.data.current = this.to; e.preventDefault(); }, }, }); const self = this; Vue.component("router-view", { render(h) { console.log(self); const component = self.routeMap[self.data.current]; return h(component); }, }); } initEvent() { window.addEventListener("popstate", () => { this.data.current = window.location.pathname; }); } } 複製代碼
過去已通過去,將來即將美好。
這不是一篇面經,除了標題之外,內容都是乾貨。
可是,下面我要寫幾句軟文了。
當還有不少人懼怕甚至畏懼面試,而瘋狂刷面經時。
你應該明白,並非會用、會實現、看過源碼的人才能經過面試。懂得創造、思考和改革的人一樣能夠。
你能夠結交志同道合的朋友,一塊兒討論框架、工具的意義。
你也能夠發現發掘業務中的痛點和本身的興趣,實現本身的想法,不斷從興趣中獲得知足,不斷進步。
你還能夠博覽羣書,從書中尋求軟件的奧義。
總之,方法有不少不少,路子很廣很廣,要看本身如何踐行。
不要由於本身愚笨而放棄本身。小廠同樣能夠過得很快樂,也能夠實現你的價值,大廠並非惟一。
你要知道你能作什麼,而不是想要什麼。
你是否有自我提升的能力?你可否承受得了 996?要選擇最適合本身的那種生活方式。
別爲了錢,而委屈了本身。
但是沒錢,生活會委屈你。
本文使用 mdnice 排版