學(xué)習(xí)使用 Vue 路由

前言:現(xiàn)在 Vue 的路由已經(jīng)開始大規(guī)模應(yīng)用在單頁面應(yīng)用上了。比較常見的就是路由網(wǎng)址中的 URL 里面的hash(#) ,這個(gè) hash(#)來源于哪里那?沒錯(cuò)就來自HTML 的錨點(diǎn)技術(shù)。大概如下原理:

HTML錨點(diǎn)

一圖勝千言。
源代碼(省略好多h6標(biāo)簽):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>#的作用</title>
</head>
<body>
    <h1><a href="#title">跳轉(zhuǎn)到標(biāo)記一 </a></h1>
    <h6>一行車馬向東疾馳,行不數(shù)里,便有數(shù)騎馬迎來,馳到車前,翻身下馬,高聲向令狐沖致意,言語禮數(shù),甚是恭敬。</h6>
    <h1><a id="title">我是標(biāo)記一</a></h1>
    <h6>一行車馬向東疾馳,行不數(shù)里,便有數(shù)騎馬迎來,馳到車前,翻身下馬,高聲向令狐沖致意,言語禮數(shù),甚是恭敬。</h6>
</body>
</html>

除了使用錨點(diǎn)技術(shù)之外,很明顯無論 React 還是 Vue 的路由大量使用了 H5 里面的,window.history,最常用的就包含window.pushState({},"",url)=>添加一條歷史記錄。window.history.replaceState()=>替換當(dāng)前URL, window.history.forward() => 瀏覽器的前進(jìn)按鈕,window.history.back() => 瀏覽器的后退按鈕,window.history.go(number)=>瀏覽器前進(jìn)后退幾步,還有一個(gè) hash 路由事件onhashchange

一、配置一個(gè)路由

Vue 官網(wǎng):https://router.vuejs.org/zh/
項(xiàng)目安裝路由:

npm install --save vue-router

項(xiàng)目目錄的結(jié)構(gòu):這次目錄的結(jié)構(gòu)做的真好看,制作方法是使用搜狗輸入法的(ctr+shift+z)制表符。

┏ components
┃   ┣ HaHa.vue
┃   ┗ XiXi.vue
┣ router
┃   ┗ router.js
┣ App.vue
┣ main.js
┗ index.html

最終的路由效果:


最終的路由效果

index.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 學(xué)習(xí)</title>
</head>
<body>
    <div id="app"></div>
    <script src = "./virtual/bundle.js"></script>
</body>
</html>

main.js 里 vue-router 配置四步走

import Vue from "vue";
import App from "./App.vue";
// ①
import VueRouter from "vue-router";
import routes from "./router/router.js";

// 使用路由②
Vue.use(VueRouter);
// ③
const router = new VueRouter({
   routes
})
new Vue({
    el:"#app",
    render(h){
        return h(App);
    },
    // ④
    router
});

router.js

// 引入組件
import XiXi from "../components/XiXi.vue";
import HaHa from "../components/HaHa.vue";
// 暴露路由需要的數(shù)組
export default [
    {path : "/XiXi",component : XiXi},
    {path : "/HaHa",component : HaHa}
];

App.vue

<template>
    <div>
        <h1>路由測(cè)試</h1>
        <div class="box">
            <p>我是一個(gè)盒子組件將會(huì)在這里顯示</p>
            <!-- 使用 router-link 組件來導(dǎo)航. -->
            <!-- 通過傳入 `to` 屬性指定鏈接. -->
            <!-- <router-link> 默認(rèn)會(huì)被渲染成一個(gè) `<a>` 標(biāo)簽 -->
            <router-link to="/HaHa">Go to HaHa</router-link>
            <router-link to="/XiXi">Go to XiXi</router-link>
            <router-link to="/">Go to home</router-link>
            <!-- 路由出口 -->
            <!-- 路由匹配到的組件將渲染在這里 -->
            <router-view></router-view>
        </div>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>
.box{
    width: 500px;
    height: 200px;
    border:20px solid #5e5ed9;
}
</style>

components 里面就是單純的兩個(gè)組件。

二、子路由

我們?cè)谧咏M件 HaHa 里面再放入孫子組件 Bar 。URL 變成http://127.0.0.1:8080/#/HaHa/bar

演示效果大約如下:


我們需要改動(dòng)的地方有:
router.js 里面在組件 HaHa 下面加入 childrren 書寫子路由。與 react 不同的是子路由 不需要在前面加上 /

// 引入組件
import XiXi from "../components/XiXi.vue";
import HaHa from "../components/HaHa.vue";
import Bar from "../components/Bar.vue";
// 暴露路由需要的數(shù)組
export default [
    {path : "/XiXi",component : XiXi},
    {
        path : "/HaHa",
        component : HaHa,
        children:[
            {path : "Bar",component:Bar}
        ]
    }
];

除此之外還需要在 HaHa.vue 組件里面放上標(biāo)簽<router-view></router-view>,用來顯示孫子組件 Bar 。

嵌套路由如何指定默認(rèn)路由:

{
    path:"user",
    name:user,
    component: ()=>import("@/components/user.vue"),
    children:[
        {
            path:"/",//path:"/"就是默認(rèn)路由,同時(shí)path:""也是,自動(dòng)訪問
            name:index,
            component: ()=>import("@/components/index.vue")
        }
    ]
}
三、兩種路由跳轉(zhuǎn)方式
  • 第一種 HTMl 方法
    就是我們上面使用的 <router-link to="/HaHa">Go to HaHa</router-link> 標(biāo)簽,這個(gè)標(biāo)簽的頁面效果就類似HTML里面的a標(biāo)簽。
  • JS 方法
    <a @click="$router.push('/HaHa/bar')">Go to HaHa</a> 我們?cè)?a 標(biāo)簽添加事件,因?yàn)榕渲眠^路由,$router就像vuex的$store一樣可以在任何子組件使用,我們只需要調(diào)用push就能指定跳轉(zhuǎn)目標(biāo)。

路由跳轉(zhuǎn)路徑可為分為兩種:字符串和對(duì)象

  1. 字符串上面我們使用的就是字符串
  2. 對(duì)象字面量方法,使用對(duì)象的時(shí)候我們可以:
    • {path:'/HaHa'},還可以傳參分為 params和query的方式,{path:'/HaHa',params:{userId:123}},{path:'/HaHa',query:{name:'hero'}}
    • {name:'haha'} 命名路由,也可以傳參也分為 params和query的方式,{path:'/HaHa',params:{userId:123}},{path:'/HaHa',query:{name:'hero'}}
四、使用路由進(jìn)行頁面布局

有時(shí)候想同時(shí) (同級(jí)) 展示多個(gè)視圖,而不是嵌套展示,就例如我們的有一個(gè)網(wǎng)頁頁面的頭部是一個(gè) Header 組件,頁面底部是一個(gè)Footer 組件,頁面中間左邊是一個(gè)側(cè)邊欄 Slider 組件。右邊是頁面內(nèi)容 Content 組件。而我們要求輸入的網(wǎng)址必須為:http://127.0.0.1:8080/#/index.html

原型圖如下:


首先我們要是用路由的重定向功能,讓網(wǎng)址一打開http://127.0.0.1:8080/#/就跳轉(zhuǎn)到http://127.0.0.1:8080/#/index.html。路由文件添加代碼:

{ path: '/', redirect: "/index.html"}

插題路由重定向是可以重定向到自己內(nèi)部的。

{
    path:"/home",
    redertec:"/index",
    children:[
        {
            path:"/index",
            name:"index",
            children:[
                
            ]
        }
    ]
}

下面來講講如何實(shí)現(xiàn),默認(rèn)已經(jīng)完成基本布局HTML大致布局代碼:

<div class="box">
            <!-- 網(wǎng)頁頭部 -->
            <header>我是Header組件</header>
            <!-- 網(wǎng)頁主體 -->
            <main>
                <aside>我是Slider側(cè)邊欄組件</aside>
                <section>我是Content組件</section>
            </main>
            <!-- 網(wǎng)頁的底部 -->
            <footer>我是Footer組件</footer>
        </div>

我們只需要把每部分單獨(dú)提出來變成組件然后使用路由進(jìn)行顯示。

  • 命名視圖法
    <header><router-view></router-view></header> 組件 router-view 不加 name 屬性在路由文件里面組件將會(huì)默認(rèn)填充到這里,如果寫了name屬性那么相應(yīng)的組件會(huì)映射到相應(yīng)的name標(biāo)簽。
    我們現(xiàn)在把內(nèi)容給提成四個(gè)組件:App.vue 配上路由就變成了
<!-- 網(wǎng)頁頭部 -->
<header><router-view></router-view></header>
<!-- 網(wǎng)頁主體 -->
<main>
    <aside><router-view name="Slider"></router-view></aside>
    <section><router-view name="Content"></router-view></section>
</main>
<!-- 網(wǎng)頁的底部 -->
<footer><router-view name="Footer"></router-view></footer>

然后我們只需要改變路由文件 router.js 文件,讓不同的路由視圖,顯示顯示對(duì)應(yīng)的組件,只需要采用組件組的形式:

// 引入組件
import Header from "../components/Header.vue";
import Footer from "../components/Footer.vue";
import Slider from "../components/Slider.vue";
import Content from "../components/Content.vue";
// 暴露路由需要的數(shù)組
export default [
    { path: '/', redirect: "/index.html"},
    { path: '/index.html', components:{
        default:Header,
        Footer,
        Slider,
        Content
    }}
];
五、面包屑導(dǎo)航
面包屑導(dǎo)航

左邊的 Slider 組件作用使用 HTML 進(jìn)行路由跳轉(zhuǎn)。代碼為:

<template>
    <div>
        <aside>
            <div>
                <router-link class="cur" to="/">首頁</router-link>
            </div>
            <div>
                <router-link class="cur" to="/index.html/yule">娛樂</router-link>
            </div>
            <div>
                <router-link class="cur" to="/index.html/news">新聞</router-link>
            </div>
        </aside>
    </div>
</template>

<script>
    export default {
        
    }
</script>

<style scoped>
.cur{
    display:inline-block;
    margin:20px 30px;
    text-decoration: none;
    color:#666;
    font-size: 16px;
}
</style>

然后去更改路由文件router.js:

{ 
    path: '/index.html',
    components : {
        default:Header,
        Footer,
        Slider,
        Content
    },
    meta:{chinese:"首頁"},
    children : [
        { path : "news" ,component : News,meta:{chinese:"新聞"}},
        { path : "yule" ,component : Yule,meta:{chinese:"娛樂"}}
    ]
}

路由元信息(meta)?
定義路由的時(shí)候可以配置 meta 字段:一個(gè)路由匹配到的所有路由記錄會(huì)暴露為 $route 對(duì)象的 $route.matched 數(shù)組。因此,我們需要遍歷 $route.matched 來檢查路由記錄中的 meta 字段。

我們?cè)黾恿藘蓚€(gè)子組件,news 和 yule(娛樂)。
再回到 Content 組件里面。進(jìn)行配置。

<div>
    當(dāng)前所在位置:
    <router-link to="/">首頁</router-link> 
    {{$router.currentRoute.matched[1] ? "→ "+$router.currentRoute.matched[1].meta.chinese : null}}
    <router-view></router-view>
</div>

猛一看可能比較懵,我們來看看 $router 身上攜帶了什么參數(shù),我們直接在生命周期beforeCreate 里面打印出來$router


就一個(gè) API 有用:currentRoute,在把currentRoute 打印出來console.log(this.$router.currentRoute);


各個(gè)參數(shù)用處大約如下:

// 路由模式
console.log(this.$router.mode);
//當(dāng)前路由的路徑
console.log(this.$router.currentRoute.fullPath);
console.log(this.$router.currentRoute.path);
// URL路徑的查詢結(jié)果index.html?name=hero后面的東西{name: "hero"}
console.log(this.$router.currentRoute.query);
// 動(dòng)態(tài)參數(shù)/:id,用戶輸入的id格式{id: "876543"}
console.log(this.$router.currentRoute.params);
// <router-view></router-view> 上的name屬性
console.log(this.$router.currentRoute.name);
// meta自定義傳入的參數(shù),可以被我們使用
console.log(this.$router.currentRoute.meta.chinese);
// matched是一個(gè)數(shù)組,里面存放著滿足當(dāng)前路徑的所有路由組件的路徑
console.log(this.$router.currentRoute.matched);

需要注意的是用到路由參數(shù)的時(shí)候,可能會(huì)失效,例如: /index.html/news 導(dǎo)航到 /index.html/yule,原來的組件實(shí)例會(huì)被復(fù)用。因?yàn)閮蓚€(gè)路由都渲染同個(gè)組件,比起銷毀再創(chuàng)建,復(fù)用則顯得更加高效。重要的是生命周期鉤子不會(huì)再被調(diào)用。在視圖中如果使用路由參數(shù)(參數(shù)是對(duì)象或數(shù)組)插入,造成結(jié)果是視圖不更新,所以聲明式寫法必須沒有引用類型。除此之外解決辦法就只能使用官方推薦的方法:

使用 watch (監(jiān)測(cè)變化) $router 對(duì)象:

  watch: {
    '$router' (to, from) {
      // 對(duì)路由變化作出響應(yīng)...
    }
  }

或者使用生命周期的 updated 來實(shí)現(xiàn)。親測(cè)可行,Vue 就是亂,官網(wǎng)上講不行,結(jié)果還是的以實(shí)際為準(zhǔn)。

六、導(dǎo)航守衛(wèi)(路由鉤子)
1. 組件內(nèi)的守衛(wèi)
  • beforeRouteEnter 進(jìn)入組件觸發(fā)
  • beforeRouteUpdate (2.2 新增) 組件復(fù)用觸發(fā)
  • beforeRouteLeave 離開組件觸發(fā)

接下來以 XiXi 組件為例,修改代碼加入路由進(jìn)入和路由離開:

<template>
    <div>
        <h1>我是嘻嘻組件</h1>
    </div>
</template>

<script>
    export default {
        // 組件內(nèi)的守衛(wèi)
        // 導(dǎo)航離開該組件的對(duì)應(yīng)路由時(shí)調(diào)用
        beforeRouteLeave (to, from , next) {
            // 這個(gè)離開守衛(wèi)通常用來禁止用戶在還未保存修改前突然離開
            const answer = window.confirm("你確定要離開當(dāng)前頁面!");
            if (answer) {
                next();//確定跳轉(zhuǎn)
            } else {
                next(false);//導(dǎo)航可以通過 next(false) 來取消
            }
        },
        beforeRouteEnter (to, from, next) {
        // 進(jìn)入該組件觸發(fā)
        // 不!能!獲取組件實(shí)例 `this`
        // 因?yàn)楫?dāng)守衛(wèi)執(zhí)行前,組件實(shí)例還沒被創(chuàng)建
        const answer = window.confirm("你確定進(jìn)入該頁面!");
            if (answer) {
                next(vm => {
                // 通過 `vm` 訪問本組件的實(shí)例
                console.log(vm)
                });//確定進(jìn)入
            } else {
                next(false);//不進(jìn)入
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>

演示結(jié)果:

組件內(nèi)的守衛(wèi)

最最重要的就是beforeRouteLeave(將要離開當(dāng)前路由),這個(gè)離開守衛(wèi)通常用來禁止用戶在還未保存修改前突然離開,簡(jiǎn)書寫文章時(shí)未保存,或者平時(shí)填寫一些表單退出時(shí)給出提示用到的就是這個(gè)。除了進(jìn)入路由和離開路由還有個(gè)特殊的路由。為此特地新建一個(gè)組件 Foo,并配置路由為:{path : "/foo/:id",component : Foo}
Foo組件的內(nèi)容為:

<template>
    <div>
        <h1>被復(fù)用的組件</h1>
    </div>
</template>

<script>
    export default {
        beforeRouteUpdate (to, from, next) {
        // 在當(dāng)前路由改變,但是該組件被復(fù)用時(shí)調(diào)用
        // 舉例來說,對(duì)于一個(gè)帶有動(dòng)態(tài)參數(shù)的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉(zhuǎn)的時(shí)候,
        // 由于會(huì)渲染同樣的 Foo 組件,因此組件實(shí)例會(huì)被復(fù)用。而這個(gè)鉤子就會(huì)在這個(gè)情況下被調(diào)用。
        // 可以訪問組件實(shí)例 `this`
        const answer = window.confirm("你確定從/foo/1 => /foo/2 !");
            if (answer) {
                next(vm => {
                // 通過 `vm` 訪問本組件的實(shí)例
                console.log(vm)
                });//確定進(jìn)入
            } else {
                next(false);//不進(jìn)入
            }
        }
    }
</script>

<style lang="scss" scoped>

</style>

在 APP 組件在添加兩個(gè)跳轉(zhuǎn)按鈕:

<router-link to="/foo/1">/foo/1</router-link>
<router-link to="/foo/2">/foo/2</router-link>

查看演示效果:


beforeRouteUpdate

每個(gè)守衛(wèi)方法接收三個(gè)參數(shù):

  • to: Route: 即將要進(jìn)入的目標(biāo) 路由對(duì)象
  • from: Route:當(dāng)前導(dǎo)航正要離開的路由。
  • next: Function:next(): 進(jìn)行管道中的下一個(gè)鉤子。next(false): 中斷當(dāng)前的導(dǎo)航。next('/') 或者 next({ path: '/' }): 跳轉(zhuǎn)到一個(gè)不同的地址。
2. 路由獨(dú)享的守衛(wèi)

可以在路由配置上直接定義 beforeEnter 守衛(wèi):此處以 HaHa 組件為例

beforeEnter: (to, from, next) => {
    // 路由獨(dú)享的守衛(wèi)
    const answer = window.confirm("路由獨(dú)享的守衛(wèi)!");
    if (answer) {
        next();//確定進(jìn)入
    } else {
        next(false);//不進(jìn)入
    }
}

查看演示:


路由獨(dú)享的守衛(wèi)
3. 全局前置守衛(wèi)

當(dāng)一個(gè)導(dǎo)航觸發(fā)時(shí),全局前置守衛(wèi)按照創(chuàng)建順序調(diào)用。
官網(wǎng)代碼案例:

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

太簡(jiǎn)單不演示了。

七、路由懶加載

當(dāng)打包構(gòu)建應(yīng)用時(shí),JavaScript 包會(huì)變得非常大,影響頁面加載。如果我們能把不同路由對(duì)應(yīng)的組件分割成不同的代碼塊,然后當(dāng)路由被訪問的時(shí)候才加載對(duì)應(yīng)組件,這樣就更加高效了。

結(jié)合 Vue 的異步組件和 Webpack 的代碼分割功能,輕松實(shí)現(xiàn)路由組件的懶加載。

使用的形式其實(shí)就是組件的函數(shù)化:
以前:

component:About

現(xiàn)在:

component: () => import('./About.vue')

這是 Vue 的路由懶加載,還有 webpack 的懶加載方式,我同學(xué)項(xiàng)目中也有人用。基本沒變,看了秒懂:

component: r => require.ensure([], () => r(require('../page/home')), 'home')

詳情請(qǐng)去查看這個(gè)博客,里面最重要的話就是:

當(dāng)然其實(shí)講了那么長(zhǎng)的 require.ensure并沒有什么用,因?yàn)檫@個(gè)函數(shù)已經(jīng)被 import() 取代了,但是考慮到之前的版本應(yīng)該有很多人都是用 require.ensure 方法去加載的,所以還是講一下,而且其實(shí) import 的執(zhí)行過程跟 require.ensure 是一樣的,只不過用了更友好的語法而已,所以關(guān)于 import 的執(zhí)行流程我也沒啥好講的了,感興趣的人看一下兩者的 API介紹就好了。

八、404 路由設(shè)置

常規(guī)參數(shù)只會(huì)匹配被/分隔的 URL 片段中的字符。如果想匹配任意路徑,我們可以使用通配符 (*):

{
  // 會(huì)匹配所有路徑
  path: '*'
}
{
  // 會(huì)匹配以 `/user-` 開頭的任意路徑
  path: '/user-*'
}

當(dāng)使用通配符路由時(shí),請(qǐng)確保路由的順序是正確的,也就是說含有通配符的路由應(yīng)該放在最后。路由 { path: '*' } 通常用于客戶端 404 錯(cuò)誤。當(dāng)使用一個(gè)通配符時(shí),$router.currentRoute.params 內(nèi)會(huì)自動(dòng)添加一個(gè)名為 pathMatch 參數(shù)。它包含了 URL 通過通配符被匹配的部分:
例如輸入錯(cuò)誤的網(wǎng)址為:http://127.0.0.1:8080/#/index
$router.currentRoute.params.pathMatch 匹配輸出為:/index

八、滾動(dòng)行為

滾動(dòng)行為:使用前端路由,當(dāng)切換到新路由時(shí),想要頁面滾到自己指定位置,或者是保持原先的滾動(dòng)位置,就像重新加載頁面那樣。

const router = new VueRouter({
   routes,
   scrollBehavior (to, from, savedPosition) {
    // return 期望滾動(dòng)到哪個(gè)的位置切換滾動(dòng)到
    return { x: 0, y: 1000 }
  }
});
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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