少妇无码太爽了在线播放_久久久综合香蕉尹人综合网_日日碰狠狠添天天爽五月婷_国产欧美精品一区二区三区四区

人參的功效(xiao)

vue-router參數,「面試必備」如何實現 VueRouter?

前(qian)端面(mian)試(shi)(shi)中(zhong),會問(wen)到非常多的(de)知識點。框(kuang)架,幾(ji)乎是(shi)(shi)必問(wen)的(de)問(wen)題之一(yi)。Vue 作(zuo)為目(mu)前(qian)最流行的(de) SPA 框(kuang)架之一(yi),是(shi)(shi)面(mian)試(shi)(shi)過程中(zhong)的(de)重頭戲。Vue Router 作(zuo)為 Vue 生態(tai)中(zhong)極其重要(yao)的(de)角色(se),也是(shi)(shi)我們(men)必須掌握的(de)一(yi)項技(ji)能。

這篇文章將介紹(shao) Vue Router 的使用,并(bing)且自己動手實(shi)現一個簡易版的 Vue Router。

Vue Router 基礎回顧

使用步驟

首(shou)先使用 vue cli 創建一(yi)個 Vue 項目來回(hui)顧(gu)一(yi)下 vue router 的使用。

全局安裝 vue cli。

npm i -g @vue/cli

安裝完成(cheng)后檢(jian)查(cha)版本是否(fou)正常。

vue --version

然后創建(jian)一個演(yan)示項目。

vue create vue-router-demo

首先使用自(zi)定義選擇。Manually select features。

vue cli 會詢問(wen)一些問(wen)題。只需要(yao)選擇三項 Babel, Router, Linter。

這樣 vue cli 會幫(bang)我們創(chuang)建 vue router 的基本(ben)代碼(ma)結(jie)構。

進(jin)入項(xiang)目并啟動項(xiang)目。

npm run serve

然后就可(ke)以在瀏覽器中(zhong)看到路(lu)由(you)的(de)效果(guo)了。

在(zai) vue 中使用 vue router 的步驟大致有如下(xia)幾步。

路由頁面

創(chuang)建路由對應(ying)的頁面。

默(mo)認在 views 文件(jian)夾中。

注冊路由插件

使用 Vue.use(VueRouter)來注冊(ce)路由插(cha)件。Vue.use 方(fang)法是專門用來注冊(ce)插(cha)件的(de),如果(guo)傳入的(de)是函數,會直(zhi)接調用。如果(guo)傳入的(de)是對(dui)象,會調用對(dui)象的(de) install 方(fang)法。

默(mo)認(ren)在 router/index.js 中。

創建路由對象

首先(xian)定義一(yi)套(tao)路由規(gui)則,路由規(gui)則是一(yi)個數組(zu),數組(zu)中(zhong)包含很(hen)多對(dui)象,每一(yi)個對(dui)象都是一(yi)個規(gui)則。對(dui)象上面會(hui)有 path 和 component 等屬性,path 代表(biao)著(zhu)路徑,compoent 代表(biao)著(zhu)渲染的組(zu)件。當瀏覽器(qi)中(zhong) path 發生變化(hua)時(shi),會(hui)渲染對(dui)應的 component 到頁面中(zhong)。

通(tong)過 new VueRouter 的方(fang)式創建對(dui)象,VueRouter 的構造函數是一個對(dui)象。要把這個對(dui)象的 routes 屬性設置為剛(gang)剛(gang)定義(yi)的路由規(gui)則。

默認在 router/index.js 中(zhong)。

注冊 router 對象(xiang)

在 new Vue 時,配置(zhi)對象(xiang)中的 router 選(xuan)項設置(zhi)為上面創建的路由(you)對象(xiang)。

創建路由組件占位

在(zai) Vue 實例指定(ding)的 el 選項對應的元素中(zhong),使用 router-view 標簽(qian)創建路由組件的占(zhan)位。路由組件每次都會渲(xuan)染到這個位置。

創建鏈接

使用 router-link 創建鏈接,通過 router-link 來改(gai)變路由。

當(dang) Vue 實例開啟 router 選(xuan)項后(hou),實例對象會多(duo)出兩(liang)個屬性,分別是(shi) $route 和 $router。

$route 是當前的路由規則(ze)對象,里(li)面存儲了路徑、參(can)數等信息。

$router 是路(lu)由實(shi)例對象,里面(mian)存(cun)儲了很多路(lu)由的(de)方(fang)法,比如 push、replace、go 等。還存(cun)儲了路(lu)由的(de)信息,比如 mode 和 currentRoute。

動態路由

假設有(you)一個(ge)(ge)用戶詳情頁(ye)(ye)面。我們(men)不會給每一個(ge)(ge)用戶都創建一個(ge)(ge)詳情頁(ye)(ye)面,因為這個(ge)(ge)頁(ye)(ye)面是通用的(de),變(bian)化(hua)的(de)只是用戶的(de) id。

首先添加一個路由。

:id 前(qian)面(mian)的路徑是固定(ding)的,:id 本身的意思就是接收一個 id 參(can)數。

component 返(fan)回(hui)的(de)是一個(ge)函數,這就是路由懶加(jia)載(zai)的(de)寫法。

也就是當這個(ge)路由(you)被(bei)觸(chu)發時,才會渲(xuan)染(ran)這個(ge)組件(jian)。當這個(ge)路由(you)沒有(you)被(bei)觸(chu)發時,不會渲(xuan)染(ran)組件(jian)。可以(yi)提(ti)高性能。

// ... other code

const routes = [

// ... other code

{

path: "/detail/:id",

name: "Detail",

component: () => import("../views/Detail.vue"),

},

];

有了路由之后,再(zai)創建一個用戶頁(ye)面。

<template>

<div>當前用戶ID:{{ $route.params.id }}</div>

</template>

<script>

export default {

name: "Detail",

};

</script>

這(zhe)就是第一種獲(huo)(huo)取(qu)動態(tai)路由(you)參(can)數(shu)的方式,通過(guo)路由(you)規則(ze),獲(huo)(huo)取(qu)參(can)數(shu)。

但是這(zhe)種方式(shi)有一個缺點,就是強制(zhi)依賴(lai) $route 才(cai)能(neng)正常工作。

可以(yi)使用另(ling)一種(zhong)方式來降低這種(zhong)依賴。

在路由(you)規(gui)則(ze)中開啟 props 屬性。

// ... other code

const routes = [

// ... other code

{

path: "/detail/:id",

name: "Detail",

props: true,

component: () => import(&quot;../views/Detail.vue"),

},

];

props 屬性(xing)的作(zuo)用(yong)是將路由中的參(can)數(shu)以 props 的形式傳入到(dao)組(zu)(zu)件中,這樣在組(zu)(zu)件內就可以通(tong)過 props 獲取(qu)到(dao)參(can)數(shu)。

<template>

<div>當(dang)前(qian)用戶ID:{{ id }}</div>

</template>

<script>

export default {

name: "Detail",

props: ["id"],

};

</script>

這(zhe)樣 Detail 這(zhe)個組件就不是(shi)必須(xu)在(zai)路(lu)由中才(cai)可以(yi)使(shi)用(yong),只要傳(chuan)遞(di)一(yi)個 id 屬性(xing),它就可以(yi)被應用(yong)到(dao)任何位置(zhi)。

所以更(geng)加(jia)推薦使用 props 的方式(shi)傳(chuan)遞路由參數(shu)。

嵌套路由

當多(duo)個路由(you)組(zu)(zu)件(jian)具(ju)有(you)相(xiang)同的內(nei)容(rong),可以把(ba)多(duo)個路由(you)組(zu)(zu)件(jian)相(xiang)同的內(nei)容(rong)提(ti)取到一個公共的組(zu)(zu)件(jian)中。

假設首頁和(he)詳情頁具(ju)有相同的(de)頭部(bu)和(he)尾部(bu)。可以提取一個 layout 組(zu)件(jian)(jian),把頭部(bu)和(he)尾部(bu)抽取到 layout 組(zu)件(jian)(jian)中,并(bing)在發生(sheng)變化的(de)位置放置一個 router view。當訪問對應的(de)路(lu)由時,會把路(lu)由組(zu)件(jian)(jian)和(he) layout 組(zu)件(jian)(jian)的(de)內容合并(bing)輸出。

假設還有一個登錄頁面,它(ta)(ta)不是需(xu)要 layout 的,所以它(ta)(ta)也不需(xu)要嵌套路由。

編寫 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>

創建(jian) Login.vue 組件。

<template>

<div>登陸頁</div>

</template>

<script>

export default {

name: "login",

};

</script>

修改(gai) app.vue 中(zhong) template 代(dai)碼(ma)塊中(zhong)的內容(rong)。

<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&quot;),

},

],

},

];

這樣當訪問
//localhost:8080/login時,會正常進(jin)入Login組件。

訪問(wen)//localhost:8080/時(shi),會首先加載/對應(ying)的Layout組(zu)件(jian)(jian),然后再加載Home組(zu)件(jian)(jian),并(bing)把(ba)Layout組(zu)件(jian)(jian)和(he)Home組(zu)件(jian)(jian)的內容(rong)進行合并(bing)。

訪問
//localhost:8080/detail/id時(shi),也(ye)會和Home加(jia)載方式一樣,先加(jia)載Layout,再加(jia)載Detail,并把(ba)id傳遞(di)進去。最后把(ba)兩個組(zu)件的內容(rong)合并。

編程式導航

除了使用 router-link 進行導航以外,我們還可(ke)以使用 js 代(dai)碼的(de)方式進行導航。

這種(zhong)需求非(fei)常(chang)常(chang)見,比如點擊一個按鈕,進(jin)行(xing)邏輯判斷后再(zai)進(jin)行(xing)導航。

常(chang)用(yong)的編(bian)程式導航 API 有(you) 4 個(ge)。分別是 $router.push、$router.replace、$router.back 和 $router.go

改造一(yi)下上(shang)面的(de)三個(ge)(ge)頁(ye)面,來體(ti)驗一(yi)下這(zhe) 4 個(ge)(ge) API。

登陸(lu)頁通過(guo)點(dian)擊(ji)登陸(lu)按鈕(niu)跳轉(zhuan)到首頁。

<template>

<div>

<div>登陸(lu)頁(ye)</div>

&lt;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>

首頁可以跳轉(zhuan)到用戶詳情頁,也可以退(tui)出(chu),退(tui)出(chu)的話跳轉(zhuan)到登陸(lu)頁,并(bing)且在瀏覽器(qi)的瀏覽歷史(shi)中不保存當前頁。

<template>

<div class="home">

<div>Home Page.</div>

<button @click="goToDetail">查看(kan)用戶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>

用(yong)戶(hu)詳(xiang)情頁(ye)中(zhong)可(ke)以回退到(dao)上(shang)一頁(ye),也可(ke)以回退兩頁(ye)。

<template>

<div>

<div>當前用戶ID:{{ id }}</div>

<button @click=&quot;back">返回</button>

<button @click="backTwo">回退(tui)兩(liang)頁</button>

</div>

</template>

<script>

export default {

name: "Detail",

props: ["id"],

methods: {

back() {

this.$router.back();

},

backTwo() {

this.$router.go(-2);

},

},

};

</script>

其中(zhong) push 方(fang)法(fa)和 replace 方(fang)法(fa)的(de)(de)用法(fa)基(ji)本上是一致(zhi)的(de)(de),都可以(yi)(yi)通過傳(chuan)(chuan)遞(di)一個(ge)字(zi)(zi)符(fu)串或者(zhe)傳(chuan)(chuan)遞(di)一個(ge)對(dui)象(xiang)(xiang)來實現(xian)頁面導航。如果傳(chuan)(chuan)遞(di)字(zi)(zi)符(fu)串的(de)(de)話,就表示頁面的(de)(de)路(lu)徑。傳(chuan)(chuan)遞(di)對(dui)象(xiang)(xiang)的(de)(de)話,會根據對(dui)象(xiang)(xiang)的(de)(de) name 屬性(xing)去尋找(zhao)對(dui)應的(de)(de)頁面組(zu)件。如果需要(yao)傳(chuan)(chuan)遞(di)參(can)數,可以(yi)(yi)拼接字(zi)(zi)符(fu)串,也可以(yi)(yi)在對(dui)象(xiang)(xiang)中(zhong)設置 params 屬性(xing)。兩者(zhe)不(bu)同之處(chu)在于 replace 方(fang)法(fa)不(bu)會在瀏(liu)覽(lan)器中(zhong)記錄(lu)當前頁面的(de)(de)瀏(liu)覽(lan)歷史,而 push 方(fang)法(fa)會記錄(lu)。

back 方(fang)法(fa)是回(hui)到上一頁,它的用法(fa)最簡單,不需(xu)要傳遞(di)參數。

go 方法可(ke)以傳遞一個(ge) number 類(lei)型的參數(shu),表示是前(qian)進還是后退。負數(shu)表示后退,正數(shu)表示前(qian)進,0 的話刷新當(dang)前(qian)頁面。

Hash 模式和(he) History 模式

Vue Router 中(zhong)的路(lu)由(you)模式(shi)有兩(liang)種(zhong),分別是(shi) hash 模式(shi)和 history 模式(shi),hash 模式(shi)會在(zai)導航欄地(di)址中(zhong)具有一個井號(hao)(#),history 模式(shi)則沒有。

兩(liang)種(zhong)模式都是(shi)由客(ke)戶端來處理(li)的,使(shi)用 JavaScript 來監聽路由的變(bian)化,根據不(bu)同的 URL 渲染不(bu)同的內容。如果需要(yao)服務端內容的話,使(shi)用 Ajax 來獲取。

表現形式的區別

從美(mei)(mei)觀(guan)上來(lai)看,history 模式更加美(mei)(mei)觀(guan)。

hash 模(mo)式的 URL。會附帶一個井號(#),如果傳遞參數(shu)的話,還需(xu)要(yao)問號(?)。

//localhost:8080/#/user?id=15753140

history 模式的鏈接。

//localhost:8080/user/15753140

但(dan)是 history 不(bu)可以直接使(shi)用,需要服(fu)務端配置支(zhi)持。

原理的區別

Hash 模(mo)式是基于(yu)錨點(dian)以及 onhashchange 事件。

History 模式是基于 HTML5 中的 History API。history 對象具有 pushState 和 replaceState 兩個方(fang)法。但是需要注(zhu)意 pushState 方(fang)法需要 IE10 以(yi)后才可以(yi)支持。在 IE10 之前的瀏覽(lan)器,只能使用 Hash 模式。

history 對象還有一個 push 方(fang)法(fa),可以改變導航欄的地(di)址(zhi),并向服務(wu)器(qi)發送(song)請求。pushState 方(fang)法(fa)可以只改變導航欄地(di)址(zhi),而不向服務(wu)器(qi)發送(song)請求。

History 模式

History 需要服務器的支持。

原因是單頁面應用中,只有一個 index.html。而在單頁面應用正常通過點擊進入
//localhost:8080/login 不會(hui)有問(wen)題(ti)。但是當(dang)刷新瀏覽器(qi)(qi)時(shi),就(jiu)(jiu)會(hui)請求服(fu)務(wu)器(qi)(qi),而服(fu)務(wu)器(qi)(qi)上不存在這個 URL 對應的資源,就(jiu)(jiu)會(hui)返回 404。

所以(yi)在服(fu)務器上應(ying)該(gai)配置除了靜態資源以(yi)外的(de)所有請求(qiu)都返回 index.html。

下面(mian)(mian)演示(shi)一下頁面(mian)(mian)匹配不(bu)到的效果(guo)。

在 views 目錄(lu)下創建 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 中添加一個不存在的(de)鏈接。

<router-link to="/video">video</router-link>

然(ran)后啟動服務器,進入首頁,點擊 video 鏈(lian)接,就(jiu)會跳(tiao)轉到(dao) 404 頁面。

這(zhe)是一個我們(men)預期想要(yao)的(de)效果。在 vue cli 默認的(de)服(fu)務(wu)器(qi)中,已經幫我們(men)配置好了。但是在我們(men)實際部署(shu)的(de)時候,仍然需要(yao)自己去配置服(fu)務(wu)器(qi)。

node.js 服務器配置

首(shou)先使用 nodejs 開發一個服務器(qi)。

創建一個(ge) server 文件(jian)夾,并(bing)初始化項目。

npm init -y

安裝項目的依賴,這里使用 express 和
connect-history-api-fallback。

express 是一(yi)個 nodejs 著名的 web 開發服務器框架。


connect-history-api-fallback 是(shi)一(yi)個處理(li) history 模式的模塊。

npm i express connect-history-api-fallback

創建并編寫 server.js 文件。

const path = require("path");

// 處理 history 模式的模塊

const history = require(&quot;connect-history-api-fallback");

const express = require("express");

const app = express();

// 注冊處理(li) history 模式的中間件

app.use(history());

// 注冊處(chu)理靜態資源的中間件

app.use(express.static(path.join(__dirname, "./web")));

app.listen(4000, () => {

console.log(`

App running at:

- Local: //localhost:4000/

`);

});

這(zhe)里把 server 項目下根(gen)目錄的(de) web 文件夾(jia)設置為網站的(de)根(gen)路徑。

當啟動 server.js 后,請求//localhost:4000/的URL都(dou)會去web文件(jian)夾下找到相應的資源。

現在(zai)打包原來的(de) vue 項目(mu)。

回到 vue 項目中,運(yun)行打包命(ming)令。

npm run build

可以得到 dist 文(wen)件夾。

將 dist 目錄(lu)中(zhong)的所(suo)有(you)內容復(fu)制到 server 項(xiang)目的 web 目錄(lu)中(zhong),就完成了項(xiang)目的部(bu)署。

接下來運行 server.js。

node server.js

打開瀏覽器,進入 detail 頁面(
//localhost:4000/detail/8)。刷新瀏覽(lan)器,一(yi)切正常(chang)。

如果(guo)不處理 history,就會出現問題。

嘗試把 app.use(history()) 注釋掉,重新啟動服務器。

同樣(yang)進(jin)入 detail 頁面,刷新(xin)瀏(liu)覽器(qi),就會(hui)(hui)(hui)進(jin)入 express 默認(ren)的 404 頁面。原因就是(shi)刷新(xin)瀏(liu)覽器(qi),會(hui)(hui)(hui)請求服務(wu)器(qi)。服務(wu)器(qi)在 web 目錄(lu)下找(zhao)不(bu)(bu)到(dao) detail/8 資(zi)源(yuan)。如果開啟了 history 處理,服務(wu)器(qi)找(zhao)不(bu)(bu)到(dao) detail/8,就會(hui)(hui)(hui)返回 index.html,客戶端會(hui)(hui)(hui)根(gen)據當(dang)前路徑渲染組件。

nginx 服(fu)務器配置

首先安裝 nginx。

可以在 nginx 官網下載 nginx 的壓縮包。

//nginx.org/en/download.html

把壓縮包解壓到不附帶中文的目錄下。

或(huo)者借助某些(xie)工具安裝,比如 brew。

brew install nginx

nginx 的命令比(bi)較簡單,常用的命令如下。

啟動

nginx

重啟

nginx -s reload

停止

nginx -s stop

壓縮(suo)包的方式安裝,nginx 的默(mo)認端口是 80,如果 80 未(wei)被占用,會正常(chang)啟動。啟動后在瀏覽器訪(fang)(fang)問//localhost即可(ke)訪(fang)(fang)問。

brew 方式安裝的 nginx 默(mo)認(ren)端(duan)口是(shi) 8080。

把(ba) vue 項目 dist 文(wen)(wen)(wen)件(jian)夾(jia)中(zhong)的內容拷(kao)貝到 nginx 文(wen)(wen)(wen)件(jian)夾(jia)中(zhong)的 html 文(wen)(wen)(wen)件(jian)夾(jia)中(zhong)。html 文(wen)(wen)(wen)件(jian)夾(jia)就是 nginx 的默認文(wen)(wen)(wen)件(jian)夾(jia)。

部署(shu)成功后,在瀏(liu)覽(lan)器(qi)中訪問(wen)項目(mu),發現會存在同樣的刷新 404 問(wen)題。

這時就需(xu)要在 nginx 的(de)配置文件中添加對應的(de)配置。

nginx 的默認配置在(zai) conf/nginx.conf 中(zhong)。

在 nginx.conf 中找(zhao)到監聽 80 的那個 server 模塊,在從中找(zhao)到 location /的位置(zhi)。

添加 try_files 配置。

location / {

root html;

index index.html index.htm;

# $uri 是 nginx 的(de)變量,就是當(dang)前這(zhe)次請求的(de)路徑(jing)(jing)# try files 會嘗試在這(zhe)個路徑(jing)(jing)下尋找(zhao)資源,如果找(zhao)不到,會繼續朝下一個尋找(zhao)# $uri/ 的(de)意思(si)是在路徑(jing)(jing)目錄下尋找(zhao) index.html 或(huo) index.htm# 最后(hou)都找(zhao)不到的(de)話,返回 index.htmltry_files$uri$uri/ /index.html;

}

修改完(wan)配置文(wen)件后,nginx 需要重(zhong)啟。

nginx -s reload

重啟后在瀏(liu)覽器中操作,一切(qie)正常。

模擬實現 Vue Router

由(you)于 history 和 hash 模(mo)式的實現很像,這(zhe)里直接使用(yong) history 模(mo)式進(jin)行模(mo)擬。

實現原理回顧

現在再次(ci)回(hui)顧一下 vue router 的工作原理。

vue router 是前端路(lu)由(you),當(dang)路(lu)徑切換時,在瀏覽(lan)器端判斷當(dang)前路(lu)徑并加載當(dang)前路(lu)徑對(dui)應的組件。

hash 模式:

URL 中#后面(mian)的內(nei)容作為路(lu)徑地(di)址

監(jian)聽 hashchange 事件(jian)

根據當前路由地址找到(dao)對應組件重新渲染

history 模式(shi):

通(tong)過 history.pushState() 方(fang)法改變(bian)地址欄

監聽 popstate 事件

根據(ju)當前(qian)路由(you)地址(zhi)找到對應(ying)組(zu)件(jian)重新(xin)渲染

分析

通過觀察(cha) vue router 的使用,可以快速(su)推斷出 vue router 是如何實現的。

下(xia)面(mian)是一(yi)個簡單(dan)的使用流程(cheng)。

// 注冊插件

Vue.use(VueRouter);

// 創建路由對象

const router = new VueRouter({

routes: [{ name: &quot;home", path: "/", component: homeComponent }],

});

// 創建 Vue 實(shi)例,注冊 router 對(dui)象(xiang)

new Vue({

router,

render: (h) => h(App),

}).$mount("#app");

首(shou)先是執行 Vue.use 注冊 VueRouter。

Vue.use 方法(fa)是(shi)用于注冊(ce)插(cha)件(jian)的(de)(de)(de),Vue 的(de)(de)(de)強(qiang)大,得益于它的(de)(de)(de)插(cha)件(jian)機(ji)制。像 VueRouter、Vuex 和一些(xie)組件(jian),都(dou)是(shi)使用插(cha)件(jian)機(ji)制實現的(de)(de)(de)。

Vue.use 方法(fa)可(ke)以接(jie)受 1 個函數或者 1 個對象(xiang),如(ru)果是函數,則直接(jie)調用該函數,如(ru)果是對象(xiang),則調用對象(xiang)上的 install 方法(fa)。這里的 VueRouter 是一個對象(xiang)。

接下來創建(jian)了(le)一個(ge) router 實例,那么 VueRouter 應(ying)該是一個(ge)構造(zao)函數或者是一個(ge)類。

結(jie)合(he)上面的分析,可以得知,VueRouter 是一個(ge)具(ju)有 install 方(fang)法的類(lei)。

VueRouter 的構(gou)造函數是一個(ge)對象(xiang),構(gou)造參數對象(xiang)會有一個(ge) routes 屬(shu)性,記錄了(le)路由的配置信息(xi)。

最后(hou)在創(chuang)建 Vue 實例的構(gou)造參數對象中傳入(ru)了(le) router 對象。

可以通過 UML 類圖來描述 VueRouter 這個類。

VueRouter UML

VueRouter UML

UML 類圖包含 3 個部(bu)分。

最上面是(shi)(shi)類(lei)的(de)名字(zi),第二部分是(shi)(shi)類(lei)的(de)實例屬性(xing),第三(san)部分是(shi)(shi)類(lei)的(de)方(fang)法,其中加號(+)表(biao)(biao)示原型方(fang)法、下劃線(xian)(_)表(biao)(biao)示靜(jing)態(tai)方(fang)法。

屬性

options:對(dui)象,用于存儲構(gou)造函(han)數中傳入的對(dui)象。

data:對象,具有(you) current 屬性,用于記錄當前路(lu)由的(de)地(di)址(zhi)。這個對象是響應式(shi)的(de)。

routeMap:對(dui)象,用于(yu)記(ji)錄路由(you)地址(zhi)和組件的對(dui)應關系。

方法

constructor:構造方法。

Install:Vue 插(cha)件機制約定的(de)靜態(tai)方法。

init:初(chu)始化函數,用于組(zu)合 initRouteMap、initComponents 和 initEvent。

initRouteMap:解析 options 中的 routes,并將規則設(she)置到 routeMap 上面。

initComponents:創建(jian) router-link 和 router-view 組件。

initEvent:監(jian)聽 data.current 變化,切換視(shi)圖(tu)。

install

使(shi)用(yong) vue cli 創建一個新的項(xiang)(xiang)目,配置選(xuan)項(xiang)(xiang)中選(xuan)擇 babel、vue router、eslint,以便用(yong)于(yu)我們測(ce)試。

當使(shi)用(yong) Vue.use()時,會首(shou)先(xian)調用(yong) install,所以先(xian)實(shi)現(xian) install。

首先要分析,install 中要實現(xian)哪幾件事情。

Vue 是(shi)否已(yi)經(jing)安裝(zhuang)了該(gai)插件,如果(guo)已(yi)安裝(zhuang),那么就不需(xu)要(yao)再次重復安裝(zhuang)。

把 Vue 的構(gou)造(zao)函數存儲到全(quan)局變量中(zhong)。因為(wei)在(zai)后面 VueRouter 的實例方(fang)法中(zhong)會(hui)用到 Vue 構(gou)造(zao)函數中(zhong)的方(fang)法,比如創建 router-link、router-view 等組(zu)件時,需要調用 Vue.components。

將創建 Vue 實例(li)(li)時傳入的(de) VueRouter 實例(li)(li)對象注(zhu)入到所有(you)的(de) Vue 實例(li)(li)上。this.$router 就是(shi)在(zai)這個(ge)地方被(bei)注(zhu)入到 Vue 實例(li)(li)上的(de)。

在 src 目錄(lu)下創(chuang)建 vue-router 目錄(lu),并在其中創(chuang)建 index.js 文件。

let _Vue = null;

export default class VueRouter {

static install(Vue) {

// 1. 判斷(duan)當前插件(jian)是否已安(an)裝

if (VueRouter.install.installed) {

return;

}

VueRouter.install.installed = true;

// 2. 把 Vue 構造函數存儲到全局(ju)變量中

_Vue = Vue;

// 3. 把創建 Vue 實例時(shi)傳入的 router 對象注入到所有 Vue 實例上

// 混入

_Vue.mixin({

beforeCreate() {

if (this.$options.router) {

_Vue.prototype.$router = this.$options.router;

}

},

});

}

}

第一(yi)步(bu)比較簡單,記(ji)錄一(yi)個是否被插件(jian),相比于(yu)全局變量,更(geng)好的(de)方式就(jiu)是在插件(jian)本(ben)身的(de) install 方法上添加一(yi)個 installed 屬性。如果已安(an)裝,直接返回。未安(an)裝,把 installed 設置為 true,繼續執行邏輯。

第(di)二步非(fei)常簡單,只需要給全局的_Vue 賦值(zhi)就可以(yi)了。

第三步(bu)比較(jiao)難,因(yin)為在這(zhe)里(li)我們并(bing)不(bu)知道什么時(shi)候(hou)會(hui)調用 new Vue,所以也獲取(qu)不(bu)到構造(zao)參(can)數中的(de)(de)(de)(de) router 對象。這(zhe)時(shi)可以借助混入來(lai)解決(jue)這(zhe)個問題(ti)。在 mixin 方(fang)法中傳(chuan)入的(de)(de)(de)(de)對象具有(you)(you) beforeCreate 方(fang)法,這(zhe)個是(shi)(shi)(shi) new Vue 時(shi)的(de)(de)(de)(de)鉤子函數,該(gai)函數中的(de)(de)(de)(de) this 指向(xiang)的(de)(de)(de)(de)就是(shi)(shi)(shi) Vue 實例(li),所以在這(zhe)里(li)可以將 VueRouter 實例(li)注入到所有(you)(you)的(de)(de)(de)(de) Vue 實例(li)上。由(you)于每(mei)個組件也是(shi)(shi)(shi)一個 Vue 實例(li),所以還需要區分是(shi)(shi)(shi) Vue 實例(li)還是(shi)(shi)(shi)組件,不(bu)然原型擴(kuo)展(zhan)的(de)(de)(de)(de)邏輯(ji)會(hui)被執行(xing)很多次。具體通(tong)過 this.$options 是(shi)(shi)(shi)否具備 router 屬(shu)性(xing)來(lai)判斷,因(yin)為只有(you)(you) Vue 實例(li)才會(hui)具有(you)(you) router 屬(shu)性(xing),組件是(shi)(shi)(shi)沒有(you)(you)的(de)(de)(de)(de)。

構造函數

接(jie)下來實現構(gou)(gou)造函數,構(gou)(gou)造函數的邏輯比(bi)較(jiao)簡單。

創(chuang)建(jian)了三(san)個(ge)實例屬性(xing)。options 用(yong)來存(cun)儲構造參(can)數(shu);routerMap 就是一個(ge)鍵值對對象,屬性(xing)名就是路由(you)地(di)址,屬性(xing)值就是組件;data 是一個(ge)響(xiang)(xiang)應式對象,具有(you)一個(ge) current 屬性(xing),用(yong)于記錄(lu)當前的路由(you)地(di)址。可以通過_Vue.observable 來創(chuang)建(jian)響(xiang)(xiang)應式對象。

export default class VueRouter {

// other code

constructor(options) {

this.options = options;

this.routeMap = {};

this.data = _Vue.observable({

current: "/",

});

}

}

initRouteMap

該(gai)函數的(de)作用是將構造(zao)函數參(can)數 options 中(zhong)的(de) routes 屬性(xing)轉換為鍵(jian)值對的(de)形式(shi)存(cun)儲到 routeMap 上。

export default class VueRouter {

// other code

initRouteMap() {

this.options.routes.forEach((route) => {

this.routeMap[route.path] = route.component;

});

}

}

router-link

接下(xia)來實現 initComponents,這(zhe)個(ge)方法(fa)主要是注冊 router-link 和(he) router-view 這(zhe)兩個(ge)組件。

initComponents 方(fang)(fang)法接(jie)收 1 個 Vue 構造(zao)方(fang)(fang)法作為參(can)數,傳入參(can)數的目(mu)的是(shi)為了減(jian)少方(fang)(fang)法和外部的依賴。

router-link 組(zu)件會接收(shou)一個字符串類型(xing)的參數 to,就是一個鏈(lian)接。router-link 本(ben)身(shen)會轉換(huan)成 a 標(biao)簽(qian),而 router-link 的內容(rong)也會被渲染(ran)到 a 標(biao)簽(qian)內。

export default class VueRouter {

// other code

initComponents(Vue) {

Vue.component("router-link", {

props: {

to: String,

},

template: `<a :href="to"><slot></slot></a>`,

});

}

}

創建(jian) init 函數,這(zhe)個函數將 initRouteMap 和 initComponents 包裝(zhuang)起來,方便使用。

然后(hou)在創(chuang)建 Vue 實例時調(diao)用 init 方法,創(chuang)建 router-link 組件(jian)。

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;

// 在這(zhe)里調用 init

this.$options.router.init();

}

},

});

}

init() {

initRouteMap();

initComponents();

}

}

現在就(jiu)可以去測試了。

將 src/router/index.js 的(de) vue-router 替(ti)換為(wei)我們自(zi)己寫的(de) vue router。

// import VueRouter from 'vue-router'

import VueRouter from "../../vue-router/index";

啟動項目。

npm run serve

打(da)開(kai)瀏覽(lan)器,會(hui)發(fa)現頁面上一片空白,但是控(kong)制臺會(hui)得到兩個錯誤。

第一個錯誤是:

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.

這(zhe)個(ge)錯誤的(de)意思是(shi)目前使(shi)(shi)用(yong)的(de)是(shi)運行時版本的(de) Vue,模(mo)板編譯(yi)器不(bu)可(ke)用(yong)。可(ke)以使(shi)(shi)用(yong)預(yu)編譯(yi)將模(mo)板編譯(yi)成渲染(ran)函(han)數,或(huo)者(zhe)使(shi)(shi)用(yong)編譯(yi)版本的(de) Vue。

Vue 的構(gou)建版(ban)本:

運(yun)行時版:不支持(chi) template 模板,需要打包的時候(hou)提前編譯(yi)。

完整版(ban):包含運行(xing)時(shi)和(he)編譯(yi)器,體積比運行(xing)時(shi)版(ban)本(ben)大 10k 左右,程序運行(xing)的時(shi)候(hou)把模板轉換成 render 函數。

第二個錯誤是 router-view 組件未定義,因(yin)為現在還沒有處理 router-view,可以忽略。

在 VueCLI 中使用完整版 Vue

Vue Cli 創(chuang)建的項目,默認(ren)使用運行時版本(ben)的 Vue,因為它的效率更(geng)高。

如果要修改 Vue Cli 項(xiang)目(mu)(mu)(mu)的(de)配(pei)置,需要在項(xiang)目(mu)(mu)(mu)根目(mu)(mu)(mu)錄下創(chuang)建(jian) vue.config.js 文件,這個文件使用 CommonJS 規(gui)范導出一個模塊。

將 runtimeCompiler 設(she)置(zhi)為(wei) true 就可以(yi)使用完整版 Vue,默認情況下(xia)這個選(xuan)項(xiang)是 false。

module.exports = {

runtimeCompiler: true,

};

然后重新(xin)啟動項目(mu),之前碰到的(de)第一(yi)個問(wen)題就得到了解(jie)決。

但是(shi)完整版本的 Vue 體積(ji)會大(da) 10k,而(er)且是(shi)運行時(shi)編(bian)譯,消耗性能(neng),不建議使用。

運行時版(ban)本 Vue render 方法(fa)

運行時版本的 Vue 不(bu)包含(han)編譯(yi)器(qi),所以也(ye)不(bu)支持 template 選項(xiang)。而編譯(yi)器(qi)的作用(yong)就是將 template 選項(xiang)轉換(huan)為 render 函數。

我們在(zai)編(bian)(bian)寫.vue 文(wen)件(jian)時,在(zai)不(bu)(bu)開啟 runtimeCompiler 時也(ye)不(bu)(bu)會編(bian)(bian)寫 render 函(han)(han)數(shu)。這(zhe)時因為(wei) Vue Cli 中配置的(de) webpack 會在(zai)代碼(ma)編(bian)(bian)譯打(da)包階段將 vue 文(wen)件(jian)中的(de) template 轉換為(wei) render 函(han)(han)數(shu),也(ye)就是預(yu)編(bian)(bian)譯。而我們編(bian)(bian)寫的(de) js 文(wen)件(jian),是沒有進行(xing)這(zhe)種預(yu)編(bian)(bian)譯的(de)。所以要(yao)在(zai)運行(xing)時版本的(de) Vue 中需要(yao)使用 render 函(han)(han)數(shu)。

首先刪除(chu)掉 vue.config.js。

修改 initComponents 函數。

export default class VueRouter {

// other code

initComponents(Vue) {

Vue.component("router-link", {

props: {

to: String,

},

// template: `<a :href="to&quot;><slot></slot></a>`

render(h) {

return h(

"a",

{

attrs: {

href: this.to,

},

},

[this.$slots.default]

);

},

});

}

}

render 函數接收一(yi)個 h 函數,h 函數的作用是創建虛(xu)擬 DOM,最終 render 將返回虛(xu)擬 DOM。

h 函數的用法有很多種,具體可參考官方文檔:
//cn.vuejs.org/v2/guide/render-function.html

重新(xin)啟(qi)動項目(mu),符合預期。

router-view

router-view 組(zu)(zu)件類似(si)于 slot 組(zu)(zu)件,提供一個占位符(fu)的(de)作用。根(gen)據不同的(de)路由(you)地(di)址,獲取到(dao)不同的(de)路由(you)組(zu)(zu)件,并渲染到(dao) router-view 的(de)位置。

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);

},

});

}

}

這樣(yang)就(jiu)完成了 router-view 組(zu)件。

但是現在(zai)去嘗試點(dian)擊超鏈接(jie),發現并(bing)不能正(zheng)常跳轉。原因(yin)是因(yin)為 a 標簽會默認(ren)請求服(fu)務器,導致頁面刷新(xin)。

所以需要(yao)阻止 a 標簽默認請求服務器的(de)行為,并使(shi)用 histor.pushState 方法改(gai)變導航欄的(de) URL,改(gai)變的(de) URL 要(yao)保(bao)存到 this.data.current 中。因(yin)為 this.data 是(shi)響應式數(shu)據。

修改(gai) 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();

},

},

});

}

}

再次回到項目中(zhong),運行項目。點(dian)擊(ji) a 標簽,就可以正常(chang)刷新(xin)頁面(mian)內(nei)容了。

initEvent

雖然上(shang)面已經實現了所有的功能,但是還存在一個小問題(ti)。

點擊瀏覽器左上角的(de)前進、后退按鈕(niu)時,只是修改了(le)地址(zhi)欄的(de) URL,頁面并(bing)沒有(you)隨之發生(sheng)改變。

解決這(zhe)個問題也很簡(jian)單。

實(shi)現思(si)路是(shi)監聽(ting) popstate 方法(fa),并在其中將 this.data.current 的(de)(de)值設(she)置(zhi)為當前導航欄的(de)(de) URL。由于 this.data 是(shi)響應式(shi)的(de)(de)數據(ju),所以當 this.data 發生變(bian)化時,所有用到 this.data 的(de)(de)組件(jian)都會被重(zhong)新渲染(ran)。

export default class VueRouter {

// other code

init() {

// other code

this.initEvent();

}

initEvent(Vue) {

window.addEventListener("popstate", () => {

this.data.current = window.location.pathname;

});

}

}

這(zhe)樣(yang)就解(jie)決了導航欄前進(jin)后退不刷新組件的小問題。

源碼

至此(ci),history 模式的(de) vue router 簡(jian)單實(shi)現已經(jing)完成。

附全部源碼:

let _Vue = null;

export default class VueRouter {

static install(Vue) {

// 1. 判斷當前插件是否已經(jing)被安裝(zhuang)

if (VueRouter.install.installed) {

return;

}

VueRouter.install.installed = true;

// 2. 把 Vue 構造函數(shu)記錄到(dao)全(quan)局變量(liang)

_Vue = Vue;

// 3. 把創建的 Vue 實例(li)時所傳入(ru)的 router 對象注(zhu)入(ru)到 Vue 實例(li)上(shang)

// 混入

_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() {

// 遍歷所有的路由規(gui)則,把路由規(gui)則解(jie)析(xi)成(cheng)鍵值對的形式 存(cun)儲到 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", () =&gt; {

this.data.current = window.location.pathname;

});

}

}

最后的話

過去已(yi)經過去,未來即(ji)將美(mei)好。

這不是(shi)一篇面(mian)經,除了標(biao)題以(yi)外,內(nei)容(rong)都是(shi)干貨。

但是(shi),下面我(wo)要(yao)寫幾句軟(ruan)文了(le)。

當還有很多人(ren)害怕甚(shen)至(zhi)畏懼(ju)面試,而(er)瘋狂(kuang)刷面經時(shi)。

你應該明白,并(bing)不(bu)是會(hui)用、會(hui)實(shi)現、看過(guo)源碼的人才能通過(guo)面試。懂(dong)得創造、思考和(he)改革(ge)的人同樣可以(yi)。

你可(ke)以結交志同道合的(de)朋(peng)友(you),一起(qi)討論框架、工(gong)具的(de)意義。

你也可以發(fa)(fa)現發(fa)(fa)掘業(ye)務(wu)中的(de)痛點和自己的(de)興趣,實現自己的(de)想法(fa),不(bu)斷從興趣中得到滿足(zu),不(bu)斷進步。

你還可以博(bo)覽(lan)群(qun)書,從書中尋求(qiu)軟件的奧(ao)義。

總(zong)之,方法(fa)有很多(duo)很多(duo),路(lu)子很廣很廣,要看自己如(ru)何(he)踐行。

不要因(yin)為自己愚笨而(er)放棄自己。小(xiao)廠一樣可以過得很快樂,也可以實現你的價(jia)值(zhi),大廠并不是(shi)唯一。

你(ni)(ni)要知道(dao)你(ni)(ni)能做什(shen)(shen)么,而不是想(xiang)要什(shen)(shen)么。

你是否有自我提(ti)高的能(neng)力?你能(neng)否承(cheng)受得了(le) 996?要選(xuan)擇最(zui)適合自己的那種生(sheng)活(huo)方式。

別(bie)為(wei)了(le)(le)錢,而委屈(qu)了(le)(le)自己。

可是沒錢,生活會委(wei)屈(qu)你。

聯系我們

聯系我們

在線咨詢:

郵件:@QQ.COM

工作時間:周一至周五(wu),8:30-21:30,節假(jia)日不休

關(guan)注微信
關注微信
返回頂部