VueRouter小手冊

賜我白日夢發表於2020-05-23

一. 瞭解router

VueRouter 是Vue的外掛,它可以通過根據位址列動態的變換進而實現對不同路由元件進行切換。

比如:http:localhost:8080/path/123?username=xxx&password=xxx#route

在上面的url中,#後面部分我們稱之為錨點,也稱為hash值,而vue-route就是根據這個錨點去動態的在不同的元件之間切換。

當然,url中的hash資訊我們是可以通過js獲取出來的,如下:

    console.log(window.location)

在控制檯上的輸入如下:

我們在日常的開發中大多數情況下是在編寫 xxx.vue, 然後這個vue檔案中會如下的三部分。這其實就是一個vue 的模版/元件, 每個元件是有name或者是path的,那VueRouter實際上要解決的問題就是根據不同的錨點找到不同的元件, 這也就是所謂的路由分發。

路由分發就面臨著幾個問題:

  1. 傳遞引數
  2. 進行事件的處理和呼叫

路由底層的實現依賴了window.history物件

<template>
	<div>
      ...
  </div>
</template>

<script>
	    ...
</script>

<style>
			...
</style>

二. 工作流程

  1. 首先我們的初始化一個前端專案
  2. 下載vuejs,他是我們的核心
  3. 下載vue-router, router是vue的外掛,因此我們要將下載好的vue-router註冊進vue
  4. vue-router中定義規則。根據這個規則可以實現根據頁面上的hash值找到對應的模版。
  5. 在頁面中可以通過a標籤定義轉發的路由
  6. 通過<router-view>對路由對應模版的內容進行佔位

三. 簡單的Demo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    <style>
        body {
            background-color: #eee;
        }

        li {
            list-style: none;
            display: inline-block;
        }

        nav {
            text-align: center;
        }

        a {
            color: #333333;
            text-decoration: none;
            display: inline-block;
            padding-left: 5px;
            text-align: center;
        }

        .content {
            text-align: center;
        }
    </style>
</head>
<body>
<div id="app">
    <nav>
        <!--四種定義請求地址-->
        <ul>
            <li><a href="#/bar"> Bar </a></li>
            <li><a href="#/about"> About </a></li>
            <li><a href="#/">Home</a></li>
            <li>
                <router-link to="/notfound">Not Found</router-link>
            </li>
            <li>
                <router-link to="/foo">Foo</router-link>
            </li>
        </ul>
    </nav>
    <hr>
    <div class="content">
        <!--通過路由,元件最終被渲染的位置-->
        <router-view></router-view>
    </div>
</div>
</body>
<script>
    // 建立模版
    const NotFound = {template: `<p>Page not found</p>`}
    const Home = {template: '<p>Home page</p>'}
    const About = {template: '<p>About page</p>'}
    const Foo = {template: '<div>foo</div>'}
    const Bar = {template: '<div>bar</div>'}


    // 將我們的模版和hash值程式繫結,封裝成物件陣列
    const routes = [
        {path: '/', component: Home},
        {path: "/about", component: About},
        {path: "/notfound", component: NotFound},
        {path: "/foo", component: Foo},
        {path: "/bar", component: Bar}
    ]

    // new路由物件
    const router = new VueRouter({
        // 將存貯有 hash和元件的陣列註冊進路由器
        routes
    })


    var app = new Vue({
        el: "#app",
        router, // ES6語法糖,相當於 router:router
        data: {
            title: "this is title"
        }
    })

</script>
</html>

實現的效果:點選nav中的標籤,下面的內容會隨著變化

上面的例子是必須要理解的,不然真的就是僅僅知道有這麼個效果,但是前因後果,以及對我們在做什麼是不清楚的。大概的思路就是,我們首先建立元件,然後為每一個元件找一個唯一的hash值組成一個物件,多個元件+路由就可以形成一個陣列,我們將這個陣列註冊進VueRouter物件,再把VueRouter物件以外掛的形式註冊進Vue物件。 此時環境就搭建好了。

我們再去點選這種連線時,瀏覽器的位址列中就會出現#xxx 的錨點

<a href="#/">Home</a>
<router-link to="/notfound">Not Found</router-link>

流程如下:

四. 理解template和route的組合

通過如下的例子理解template和route是如何配合使用的, 我把理解寫在程式碼中的註釋上

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    <style>
        body {
            background-color: #eee;
        }

        li {
            list-style: none;
            display: inline-block;
        }

        nav {
            text-align: center;
        }

        a {
            color: #333333;
            text-decoration: none;
            display: inline-block;
            padding-left: 5px;
            text-align: center;
        }

        .content {
            text-align: center;
        }
    </style>
</head>
<body>
<div id="app">
    <nav>
        <!--四種定義請求地址-->
        <ul>
            <li><a href="#/bar"> 靜態模版 </a></li>
            <li><a href="#/dynamic"> 動態模版 </a></li>
        </ul>
    </nav>
    <hr>
    <div class="content">
        <!--通過路由,元件最終被渲染的位置-->
        <router-view></router-view>
    </div>
</div>
</body>
<script>
    // 靜態模版,之所以叫他靜態模版是因為我們可以直接看到它裡面的資料全部是寫死的,換句話說,這中靜態模版除非我們真的僅僅是做一個展示頁面,不需要和後端互動資料。除此之外,再無他用
    const Bar = {template: '<div>bar</div>'}

    // 動態模版
    // 可以看到,這種模版其實就是我們在開發中大量自己編寫的模版,一般他們都會被抽出去單獨稱為一個 xxx.vue的檔案。在這裡我們可以得心應手的編寫和後端互動的邏輯,作為一個完整vue元件的它擁有包括vue元件生命週期在內的所有函式
    // 第一個問題:這個動態元件是如何註冊進Vue中的呢? 
    // 回答: 往下看看下面new Vue()部分的程式碼。將當前的動態元件註冊在Vue的components部分即可。
    
    // 第二個問題:如何找到這個動態元件並將其渲染在頁面上的呢?
    // 回答:首先來說,路由的跳轉肯定是依賴於瀏覽器地址蘭中 hash值的變化,所以我們得先通過a標籤,或者router-link標籤傳送請求,讓瀏覽器的位址列hash值發生變化,具體的編碼往上看。當位址列中hash值發生變化後vue-router拿著變化的hash值去找對應的元件,找不到就報錯,找到了就渲染
    
    // 第三個問題:vue-router獲取到hash值後,是和Vue物件的components中的註冊資訊的name比對,還是去跟vue-router中的path資訊去比對? 還是去跟vue-router中的定義的name去比對呢?
    // 回答:和vue-router中的path比對,命中後才算數
    
    // 第四個問題:vue物件的components部分的name有什麼用呢?
    // 回答:能問這個問題,恐怕你是忘記了,元件需要對映成標籤才能用,而這個name就是標籤名,是可以自定義的
    
    // 第五個問題:VueRouter中的路由物件中的name叫啥呢?
    // 回答:首先這個router並不是必填項,如果有了那麼,我們稱其為具名路由
    
    // 第六個問題:當我們把如下的部分單獨抽取成單獨的檔案時,如果對外暴露這個元件呢?
    // 回答:這個問題在下面通過一張圖片直觀的看出來。
    const Home = {
        template: '<div>{{title}}</div>',
        data: function () {
            return {
                title: "this is a title"
            }
        },
        methods: xxx ,
      	computed: xxx ,
   
    }

    // 將我們的模版和hash值程式繫結,封裝成物件陣列
    const routes = [
        {path: "/bar", component: Bar},
        {path: "/dynamic", component: Home}
    ]

    // new路由物件
    const router = new VueRouter({
        // 將存貯有 hash和元件的陣列註冊進路由器
        routes
    })

    var app = new Vue({
        el: "#app",
        components:{
          "dynamic":Home
        },
        router, // ES6語法糖,相當於 router:router
    })

</script>
</html>

實現的效果如圖:

如何將動態元件抽取出去?如何匯入其他抽取出去的元件使用?

像上圖中我們匯入其他的元件後,註冊進當前元件的 components部分,就是,通過這種方式vue可以將元件對映成標籤。

其實現在就能小結一下,畫一張不完整的圖,來理解這個過程

五. Vue-Router-GoBack記錄返回

Vue-Router-GoBack指的是路由的記錄返回。比如我們依此點選路由: 從1 跳轉到 2 跳轉到 3 跳轉到 4 ,goback就可以實現路由分別從 4 3 2 1會退回來。

vue-router-goback底層依賴的就是瀏覽器的內建物件 window.history

問題1: window.history的作用。

可以很直觀的認為它裡面會儲存著瀏覽器位址列中曾經訪問過的地址

常用的api如下:

window.history.forword() // 前進
window.history.back() // 回退

當然這兩個api能起作用的前提是,瀏覽器中確實存在歷史記錄才行。

問題2: vue-router-goback 如果實現回退呢?

如下:

// 一個{component + path}是一個路由物件
// 如下程式碼中的 this.$router即路由器物件,也就是多個路由物件的集合
window.history.length > 1 ? this.$router.go(-1) : this.$router.push("/")

不怕多嘴,因為必須得知道:路由的切換是根據瀏覽器位址列url中的hash值而定的

在早期的vue中,通過設定a標籤中的href屬性以#/開頭,來實現瀏覽器位址列的變化

vue2.x後,router-link標籤的誕生,就是為了替換掉a標籤

看下router-link的原始碼如下:

router-link以一個指令標籤的方式被註冊在vue上,name是RouterLink,實體是 Link

不知道大家有沒有多想一下,因為如果是一個自定義的元件的話,我們想將其對映成標籤,在html元素中使用,需要我們手動的將把它註冊在vue元件的 components部分。 上面標黃色的部分,就是為什麼我們可以直接使用類似下面的標籤

<router-view> </router-view>
<router-link to=“/xxx/”> </router-link>

看看Link的詳情

模擬實現router-link

   <div>
        <my-link to="/bar"> 自定義指令 </my-link>
    </div>

   <script>
	  // 自定義comonent
    const myLink = {
        props: [
            'to'
        ],
        template:"<a :href='\"#\"+to'><slot></slot></a>"
    }
    
    Vue.component("my-link", myLink)
    ...
	 </script>


七. 具名路由

還是這個例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--    <script src="https://unpkg.com/vue/dist/vue.js"></script>
        <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>   -->
    <script src="./vue/dist/vue.js"></script>
    <script src="./vue-router/dist/vue-router.js"></script>
    <style>
        body {
            background-color: #eee;
        }

        li {
            list-style: none;
            display: inline-block;
        }

        nav {
            text-align: center;
        }

        a {
            color: #333333;
            text-decoration: none;
            display: inline-block;
            padding-left: 5px;
            text-align: center;
        }

        .content {
            text-align: center;
        }
    </style>
</head>
<body>
<div id="app">
    <nav>
        <!--四種定義請求地址-->
        <ul>
            <li><a href="#/bar"> 靜態模版 </a></li>
            <li>
            <router-link :to={name:'Dynamic'}> 動態模版</router-link>
            </li>
            <li>
                <router-link v-bind:to={name:'Dynamic'}> 動態模版</router-link>
            </li>
        </ul>
    </nav>
    <hr>
    <div class="content">
        <!--通過路由,元件最終被渲染的位置-->
        <router-view></router-view>
    </div>
</div>
</body>
<script>

    // 靜態模版
    const Bar = {template: '<div>bar</div>'}

    // 動態模版
    const Home = {
        template: '<div>{{title}}</div>',
        data: function () {
            return {
                title: "this is a title"
            }
        }
    }

    // 將我們的模版和hash值程式繫結,封裝成物件陣列
    const routes = [
        {path: "/bar", component: Bar},
        {path: "/dynamic",name: "Dynamic", component: Home}
    ]

    // new路由物件
    const router = new VueRouter({
        // 將存貯有 hash和元件的陣列註冊進路由器
        routes
    })

    var app = new Vue({
        el: "#app",
        components: {
            "com-dynamic": Home
        },
        router, // ES6語法糖,相當於 router:router
    })

</script>
</html>

具名路由就是說我們在建立一個route時,給他新增上name屬性。

const routes = [
	{
		path: '/xxx',
		component: xxx,
		name: 'yyy'
	},
]

然後我們在頁面上使用這個路由時,可以route-link的to的動態資料繫結,將name傳遞給他。

// 這時一定要使用動態資料繫結
<router-link v-bind:to={name:'Dynamic'}> 動態模版</router-link>
// v-bind可以簡寫省略
<router-link :to={name:'Dynamic'}> 動態模版</router-link>

其實到這裡具名路由到底是怎麼回事就已經說完了,然後下面引出一個問題?

問:為什麼一定得v-bind或者 :to這種動態資料型別繫結呢?

回答:

首先說,這個v-bind,它肯定是vue能識別的屬性,它的作用就是:實現將model中的data單向的綁在在view上,當vue解析到這個v-bind屬性時,知道這是個單向動態繫結,就不會使用後面的整體作為一個字串去使用,而是去解析他,使用屬性中的value作為路由值

其實你可以試一下,如果我們不使用動態資料繫結,vue肯定是不會解析{name:"xxx"} , 於是我們發出的請求就成了下面的樣子。

八. 路由啟用狀態 ActiveClass

參照下圖:

知道了這個屬性叫啥,我們就可以進一步對齊進行覆蓋,修飾。

比如:將啟用狀態的文字顏色改成紅色

        .router-link-active {
            color: #E95659;
        }

更進一步:可以進入到<router-link><router-link>標籤的原始碼中。

在props塊中定義了上面藍線圈出來的屬性,既然在props裡面說明它可以接受父元件(通過資料繫結,或者是路由傳參)傳遞過來的值,誰是父元件呢? 這個問題和沒問一樣,在哪個vue標籤中使用route-link標籤,誰就是它的父元件唄

我們主要是看下這個叫activeClass的屬性,然後它可以接受一個String型別的值。

多想一步,知道這個可以做什麼呢? 其實就是意味著我們可以自定義 activeClass的name,換句話說,就是用我們自己定義的名字,替換掉它預設的名字 router-link-active

如下:

  <router-link active-class="my-active-class" v-bind:to={name:'Dynamic'}> 
    	動態模版
  </router-link>

再補充一點:

九. 路由的重定向問題

點選進入參考連結

路由的重定向問題解決了一個什麼事呢?

假設,使用者在瀏覽器的位址列中隨意輸入了一個地址

http://localhost:63343/bower_components/123.html?#/dynamic123123

這時,我們沒有任何一個元件對應的hash值是dynamic123123

為了提高使用者的體驗,我們就可以這樣處理:當使用者輸入一個不存在的hash值時我們將其重定向到指定的元件。

做法如下:

    const routes = [
        {path: "/bar", component: Bar},
        {path: "/dynamic",name: "Dynamic", component: Home},
        {path: "*", component: XXX}
    ]

如果使用者輸入的hash值可以在VueRouter中匹配到了相關的元件,直接渲染那個元件就好了。

如果使用者輸入的hash值沒有匹配到任何hash,就會來到最後的*,這個*可以匹配任意hash,緊接著就可以將XXX元件呈現給使用者

十. 路由的引數和動態路由

點選檢視參考文件

實現下面的demo,完成對路由引數的傳遞,動態路由的學習。

示例程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./vue/dist/vue.js"></script>
    <script src="./vue-router/dist/vue-router.js"></script>
    <style>
        body {
            background-color: #eee;
        }

        li {
            list-style: none;
            /*display: inline-block;*/
        }

        nav {
            text-align: center;
        }

        a {
            color: #333333;
            text-decoration: none;
            /* display: inline-block;*/
            padding-left: 5px;
            text-align: center;
        }

        .content {
            text-align: center;
        }

        .router-link-active {
            color: #E95659;
        }
    </style>
</head>
<body>
<div id="app"> 
    <nav>
        <ul>
            <li v-for="student in students">
                <span>{{student.id}} : {{student.name}}</span>
                <router-link 
                  :to='{"name":"stu",params:{"id":student.id,"name":student.name}}'>
                  檢視詳情
                </router-link>
            </li>
        </ul>
    </nav>
    <hr>
    <div class="content">
        <router-view></router-view>
    </div>
</div>
</body>
<script>

    // 接受傳遞進來的引數
    const Student = {
        template: '<div><span>學號:{{student.id}}</span> <span>姓名:{{student.name}}</span> </div>',
        computed: {
            student: function f() {
                var paramId = this.$route.params.id
                var paramName = this.$route.params.name
                return {
                    name: paramName,
                    id: paramId
                }
            }
        },
        // data 有如下兩種寫法
        data: function () {
            // data中要返回一個物件
            return {

            }
        }
    }

    // 將我們的模版和hash值程式繫結,封裝成物件陣列
    const routes = [
        {path: "/student/:id/:name",name:'stu', component: Student},
    ]

    // new路由物件
    // router註冊進VueRouter的過程中會產生一個 $router 物件,這個物件就是路由器物件, 通過this.$router可以獲取到它,
    // 而這個$router物件就叫做路由器物件,他是所有的$route物件的全集,它裡面每一個單獨的個體都是一個$route物件
    // 這個$route物件就是一個單獨的路由例項,包含path + component
    // 同時我們可以在像路由傳遞引數, 傳遞的引數最終可以通過 this.$route.params.xxx 獲取到, 這個xxx,我們傳遞的什麼,這裡就能獲取到什麼
    const router = new VueRouter({
        // 將存貯有 hash和元件的陣列註冊進路由器
        routes
    })

    var app = new Vue({
        router, // ES6語法糖,相當於 router:router
        data: {
            students: [
                {id: 1, name: 'tom1'},
                {id: 2, name: 'tom2'},
                {id: 3, name: 'tom3'},
                {id: 4, name: 'tom4'},
            ]
        }
    }).$mount("#app")

</script>
</html>

像這個需求肯定是很常見的需求,在業務方面的需求就是,前端通過ajax向後端傳送請求,拉去學生的資訊,拉去到的資料肯定會被存放在Vue中的Data物件中。 也就是上面的 students中。

基於vue元件化的設計風格,我們將展示學生詳情的頁面抽取出去做成一個vue的動態元件。

    // 接受傳遞進來的引數
    const Student = {
        template: '<div><span>學號:{{student.id}}</span> <span>姓名:{{student.name}}</span> </div>',
        computed: {
            student: function f() {
                var paramId = this.$route.params.id
                var paramName = this.$route.params.name
                return {
                    name: paramName,
                    id: paramId
                }
            }
        },
        // data 有如下兩種寫法
        data: function () {
            return {}
        }
    }

我們需要關心的就是,如何向通過路由向其他元件傳遞引數

推薦的做法是: <router-link></router-link> + 具名路由(給 route新增一個name屬性)

 <li v-for="student in students">
                <span>{{student.id}} : {{student.name}}</span>
                <router-link 
                  :to='{"name":"stu",params{"id":student.id,"name":student.name}}'>
                  檢視詳情
   						  </router-link>
</li>

其實到這裡引數的傳遞就已經實現了,然後我們再捋一捋,引數的傳遞到底是怎麼回事???

首先說的就是路由器物件,也就是 $router 物件。當我們把VueRouter註冊進Vue時,就會產生一個 $router 物件。(其實就是上面程式碼中,我們new的VueRouter物件) 通過this.$router可以獲取到,這個物件是一個集合,它裡面有很多 $route 物件,$route物件就是 path+component物件的結合體。

其中每一個 $route 物件中都一個params屬性,通過this.$route.params可以獲取到,這個params屬性中就儲存著我們傳遞給他的所有的引數,也就是下圖中黃色線圈出來的引數

具體到那個引數我們通過this.$route.params.xxx 來獲取,這個xxx,在Router中的path部分使用 :xxx 定義。


通過路由傳遞引數可以通過 this.$route.params獲取出來,還可以通過像下面這樣,使用props進行對映。然後能直接使用通過路由傳遞過來的引數。

    // 接受傳遞進來的引數
    const Student = {
        template: '<div><span>學號:{{id}}</span> <span>姓名:{{name}}</span> </div>',
        // 通過將props,將路由中  {path: "/student/:id/:name",name:'stu', component: Student},
        // 將如上的id,name對映成下面的屬性
        props: [
         "id", "name",
        ],
        // data 有如下兩種寫法
        data: function () {
            // data中要返回一個物件
            return {

            }
        }
    }

props也會支援物件模式如下:

這種方式不會經常使用,因為它的props是我們硬編碼上去的一個物件。它的值不能動態的改變。

    const routes = [
        {   path: "/student",
            name:'stu',
            component: Student,
            props:{"id":"123","name":"tomcat"}
        },
    ]

props支援函式式

    // 將我們的模版和hash值程式繫結,封裝成物件陣列
    const routes = [
        {   path: "/student/:id/:name",
            name:'stu',
            component: Student,
            props:function (router) {
                console.log(router)
                return router.params
            }
        },
    ]

其實你看這種函式的方式和方面直接寫一個物件的模式相仿,這個函式必須把這個物件的params返回出去。否則元件中的props快中的屬性是不能被附上值的。

通過函式的方式顯然要更靈活一些,畢竟可以在中間做一些其他的操作

此外:console.log(router)列印結果如下:

十一. 程式設計式路由導航

點選檢視參考連結

問:程式設計式路由導航說啥意思呢?

答:

就是用寫程式碼的方式控制路由的跳轉,前面的我們實現路由的跳轉不是使用a標籤就是使用 vue-link標籤實現。這種方式配合css樣式可以裝飾成按鈕,以使用者可見的狀態存在。

那麼程式設計式路由導航,其實說的是通過使用 諸如 this.$router.push(xxx)的方式去實現路由的跳轉,通常我們將其放置在事件的回撥裡面,由我們程式碼中邏輯去控制該不該進行路由的跳轉。

本質上,宣告式路由導航和程式設計式路由導航是一樣的

再看一下這個router.push(location, onComplete?, onAbort?)方法。

這裡的router === this.$router

為什麼說他們相等呢? this就是vm物件,之所以他們相等,是因為我們在下面建立Vue時,使用router給Vue的router賦值了。

 const router = new VueRouter({
        // 將存貯有 hash和元件的陣列註冊進路由器
        routes : [
            {path: "/student/:id/:name",name:'stu', component: Student},
        ]
    })

    var app = new Vue({
        router, // ES6語法糖,相當於 router:router
        data: {
            students: [
                {id: 1, name: 'tom1'},
            ]
        }
    }).$mount("#app")

使用 router.push 方法。這個方法會向 history 棧新增一個新的記錄,所以,當使用者點選瀏覽器後退按鈕時,則回到之前的 URL。

push方法常見的使用方式有如下幾種:

// 字串
// 下面兩種方式是等價的,都是根據path進行路由的切換
router.push('home')  ===  router.push({ path: 'home' })

// 根據路由名稱進行跳轉,如這個user,她是路由的名稱
// 注意這個name指的的在元件註冊進VueRouter中時給的那個名字
// {path:"xxx",name:"yyy",component:ZZZ} 
// 		 -- name,而不是在元件中定義中export的name
//		 -- params 是將要傳遞給路由的引數物件集合 
router.push({ name: 'user', params: { userId: '123' }})

// 帶查詢引數,變成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

//也可以直接這樣
router.push("/register?plan=private")

問:向params傳參和向query傳參的區別是什麼???

  • 比如我們這麼寫:

    this.$router.push({name:'user',params:{userID:'123'}})
    

    最終的再url位址列上的表現是: xxx/user/123

  • 如果我們這麼寫:

    this.$router.push({pash:'register',query:{plan:'private'}})
    

    最終在位址列上的表現上:xxx/register?plan=private

但是無論我們選擇使用哪些方式,最終在我們都可以通過this.$router.params將其獲取出來。

最後補充和this.$router.push相似的還有replace,表示替換。

十二. 命名檢視

問:什麼是命名檢視?

具名路由指的是我們可以為<router-view>標籤指定具體的名字,梳理一下思路,前面我們通過a標籤或者是<router-link>標籤進行頁面路由的跳轉時,通常會留一個沒有名字的<router-view> 佔位,vue會自動根據當前瀏覽器位址列上的hash值找到相對應的模版,然後渲染上去。

也就是說,<router-view> 本質上就是一個佔位符。也就是元件最終被渲染的位置。

那麼命名檢視其實就是為<router-view>標籤指定好具體的名字,讓他僅僅為hash的路由元件佔位。

問:這樣做有什麼好處呢?

好處肯定是有的,如果我們這樣做,那麼原生的html程式碼就讓我們抽取成vue的元件了。

問:命名檢視常用在什麼場景中?

顯然易見,如果我們將其用在佈局和元件的拆分上,我們的程式碼將會很優雅。

舉個例子: 如下程式碼是中規中矩的html程式碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./vue/dist/vue.js"></script>
    <script src="./vue-router/dist/vue-router.js"></script>
    <style>
        * ,#app{
            margin: 0;
            padding: 0;
        }
        a {
            color: #333333;
            text-decoration: none;
            /* display: inline-block;*/
            padding-left: 5px;
            text-align: center;
        }
        div {
            margin-bottom: 10px;
        }
        .header {
            height: 50px;
            background-color: #EEEEEE;
        }

        .banner {
            background-color: aqua;
            float: left;
            margin-right: 5px;
            height: 300px;
            width: 29%;
        }
        .content {
            background-color: yellow;
            float: right;
            height: 300px;
            width: 70%;
        }

        .footer {
            height: 50px;
            background-color: #E95659;
        }

    </style>
</head>
<body>
<div id="app"> 
   <div class="header"></div>
   <div style="overflow: hidden">
       <div class="banner"></div>
       <div class="content"></div>
   </div>
   <div class="footer"></div>
</div>
</body>
<script>
</script>
</html>

最終的渲染圖:

然後通過命名檢視,優化程式碼如下:

可以看到,上面程式碼中的html程式碼全部被抽取出去換成了vue元件,取而代之的是具名檢視元件<router-view name="xxx"> (具名視訊只會為特定名稱的元件佔位)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./vue/dist/vue.js"></script>
    <script src="./vue-router/dist/vue-router.js"></script>
    <style>
        *, #app {
            margin: 0;
            padding: 0;
        }

        a {
            color: #333333;
            text-decoration: none;
            padding-left: 5px;
            text-align: center;
        }

        div {
            margin-bottom: 10px;
        }

        .header {
            height: 50px;
            background-color: #EEEEEE;
        }

        .banner {
            background-color: aqua;
            float: left;
            margin-right: 5px;
            height: 300px;
            width: 29%;
        }

        .content {
            background-color: yellow;
            float: right;
            height: 300px;
            width: 70%;
        }

        .footer {
            height: 50px;
            background-color: #E95659;
        }


    </style>
</head>
<body>
<div id="app"> 
    <router-view class="view header" name="one">123</router-view>
    <div style="overflow: hidden">
        <router-view class="view banner" name="two"></router-view>
        <router-view class="view " name="three"></router-view>
    </div>
    <router-view class="view footer" name="four">456</router-view>
</div>
</body>
<script>

    const router = new VueRouter({
        routes: [
            {
                path: '/',
                components: {
                    one: {
                        template: "<div >header</div>"
                    },
                    two: {
                        template: "<div class='header'>header</div>"
                    },
                    three: {
                        template: "<div class='content'>content</div>"
                    },
                    four: {
                        template: "<div>footer</div>"
                    }
                }
            }
        ]
    })


    var app = new Vue({
        el: "#app",
        router,
        data: {
            title: "this is title"
        }
    })

</script>
</html>

效果圖:

問: <router-view class="view xxx" name="yyy"> 中的xxx和yyy是什麼?

xxx:是為當前元件繫結class屬性,當然也可以不這樣指定,可以在具體的vue元件中去新增css樣式

yyy:就是命名檢視中的,它和components中的各個元件名一一對應。

十三. 巢狀路由

參考連結

看如下的demo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./vue/dist/vue.js"></script>
    <script src="./vue-router/dist/vue-router.js"></script>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        nav {
            height: 60px;
            background-color: #eee;
        }

        li {
            list-style: none;
            display: inline-block;
            padding-left: 10px;
            line-height: 60px;
        }

        nav a {
            text-decoration: none;
            color: black;
            padding-left: 10px;
            padding-right: 10px;
        }

        nav a:hover {
            display: inline-block;
            color: #ffffff;
            background-color: #444444;
        }
    </style>
</head>
<body>
<div id="app">
    <nav>
        <router-view></router-view>
    </nav>

</div>

</body>

<script>
    const navComponent = {
        template: `<div>
        <div>
            <ul>
                <li><a href="#/index">首頁</a></li>
                <li><a href="#">文章</a></li>
                <li><a href="#">友情連結</a></li>
                <li><a href="#">點我關注</a></li>
            </ul>
        </div>
        <div>
            <router-view></router-view>
        </div>
    </div>`,
        name: "nav"
    }

    const indexComponent = {
        template: `<div> this is 首頁 -->  index</div>`
    }

    const router = new VueRouter({
        // todo  如果說路由沒有載入出來,也不報錯,八成是VueRouter的這個屬性名被寫錯了
        routes: [
            {
                path: "/",
                component: navComponent,
                children: [
                    {
                        path: "/index",
                        component: indexComponent
                    }
                ]
            },
        ]
    })

    var app = new Vue({
        el: "#app",
        router: router,
        data: {}
    })
</script>

</html>

最終實現的效果就是:當我們點選首頁時,在nav標籤的下面渲染出首頁元件的子元件。

效果圖:

說一下需要注意的地方:像下面樣通過children塊為當前路由註冊子路由。

const router = new VueRouter({
        // todo  如果說路由沒有載入出來,也不報錯,八成是VueRouter下面這個屬性名被寫錯了
        routes: [
            {
                path: "/",
                component: navComponent,
                children: [
                    {
                        path: "/index",
                        component: indexComponent
                    }
                ]
            },
        ]
    })

十四. params和query

首先:

第一:VueRouter根據瀏覽器位址列中的hash值去匹配並渲染元件

第二:url中的含有:網路協議+埠+params+query

params+query相當於是hash

比如這個:

http://localhost:63342/untitled#/index/12?id=10&name=tom

params : /index/12

query : ?id=10&name=tom

  • params引數的傳遞其實在上文中第十節路由引數傳遞模組已經說了多幾種情況了,這裡不再重複說

  • 那麼query模式傳遞的引數一般怎麼處理呢?

    參考如下程式碼:?id=1&name=tom的資訊被封裝進了router的query部分

      const indexComponent = {
            template: `<div> id:{{id}} name:{{name}}</div>`,
            props:["id","name"]
        }
    
        const router = new VueRouter({
            // todo  如果說路由沒有載入出來,也不報錯,八成是VueRouter的這個屬性名被寫錯了
            routes: [
                {
                    path: "/",
                    component: navComponent,
                    children: [
                        {
                            path: "/index",
                            component: indexComponent,
                            props:function (router) {
                                console.log(router)
                                return router.query
                            }
                        }
                    ]
                },
            ]
        })
    
    

    router的列印結果如圖:

當然,它和params的使用者幾乎是相同的

總結一下,其實就是url中的path部分和引數部分,會被分別封裝進route物件的不同屬性中。這僅僅是為了可以實現兩種引數的區分,最終的目的都是為了可以傳遞引數。以及實現路由的跳轉

十五. 導航守衛

導航守衛也被稱作路由鉤子函式,他們指的都是router物件中的回撥函式,他們會在路由所處的不同狀態被回撥

點選檢視超詳細參考連線

如下是簡單的demo

為每個路由單獨寫回撥

    const router = new VueRouter({
        // todo  如果說路由沒有載入出來,也不報錯,八成是VueRouter的這個屬性名被寫錯了
        routes: [
            {
                path: "/",
                component: navComponent,
                children: [
                    {
                        path: "/index",
                        component: indexComponent,
                        props:function (router) {
                            console.log(router)
                            return router.query
                        },
                        beforeEnter:function (to,from,next) {
                            console.log("to: ",to)
                            console.log("form: ",from)
                            // next是一個函式
                            console.log("next: ",next)
                            // next(true)  表示進行路由的跳轉
                            // next(false)  表示不進行路由的跳轉
                        }
                    }
                ]
            },
        ]
    })

當發生路由跳轉時,beforeEnter會被回撥,檢視控制檯的列印結果:

通過next函式發起路由的跳轉,通常會使用這個特性幹什麼事呢? 比如我們可以在路由的跳轉之前判斷一下使用者是又已經登陸了,是否有許可權。

我們可以將其定義在模版中:

    const indexComponent = {
        template: `<div> id:{{id}} name:{{name}}</div>`,
        props:["id","name"],
        beforeRouteEnter:function (to,from,next) {
            console.log("to: ",to)
            console.log("form: ",from)
            // next是一個函式
            console.log("next: ",next)
            // next(true)  表示進行路由的跳轉
            // next(false)  表示不進行路由的跳轉
        },
        beforeRouteLeave:function (to,form,next) {
            console.log("to: ",to)
            console.log("form: ",from)
            // next是一個函式
            console.log("next: ",next)
            // next(true)  表示進行路由的跳轉
            // next(false)  表示不進行路由的跳轉
        }
    }

十六. 路由元資訊

路由的元資訊指是路由的 meta屬性。注意我們說的是路由而不是路由器。

路由:this.$route

路由器:this.$router

常見的我們可以基於路由的元資訊做安全驗證相關的操作:點選看相關文章

下面再補充下,官網提供的demo :點選進入參考連結

首先貼出VueRouter部分的程式碼:

    const router = new VueRouter({
        // todo  如果說路由沒有載入出來,也不報錯,八成是VueRouter的這個屬性名被寫錯了
        routes: [
            {
                path: "/",
                component: navComponent,
                children: [
                    {
                        path: "/index",
                        component: indexComponent,
                        props: function (router) {
                            console.log(router)
                            return router.query
                        },
                        meta: {requiresAuth: true}

                    }
                ]
            },
        ]
    })

    router.beforeEach((to, form, next) => {
        console.log("to: ", to)
        console.log("form: ", form)
        next()
    })

最終可以實現的效果如圖:

貼著張圖是啥意思呢?其一是可以比較完成的看到route的各個屬性,其二是我們可以看到他有個屬性叫做match 它是個陣列。裡面記錄著匹配到的route的資訊。 看上圖的目的很大一部分原因是去看看這個matched屬性是個陣列。因為在官方的demo中編碼如下:

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      children: [
        {
          path: 'bar',
          component: Bar,
          // a meta field
          meta: { requiresAuth: true }
        }
      ]
    }
  ]
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    // this route requires auth, check if logged in
    // if not, redirect to login page.
    if (!auth.loggedIn()) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next() // 確保一定要呼叫 next()
  }
})

在官方的demo中,路由的全域性守衛使用到了這個matched屬性以及Array的some方法,some方法的入參接受一個函式,官網demo中的實現採用了ES6的新特性去實現的。

致至為止,相信我們都能很好的理解這個demo了。

十七. VueRouter中的資料獲取

點選進入參考連結

VueRouter中的資料的指的是我們何時像後端傳送請求,將元件中需要的資料獲取到進而渲染元件。

通常來說有兩個時機:

  • 導航完成之後獲取

    導航完成之後說明我們已經找到對應的元件了。而元件中又需要渲染一些動態的資料,這時我們只能選擇在元件相應的生命週期回撥中(比如created,mounted)向後端傳送請求完成拉去資料。

    如果想讓使用者有個良好的體驗,我們可以新增上進度條,或者是loding標記。

  • 導航完成之前渲染

    這種方式指的是在路由的守衛中完成資料的拉取,資料獲取到之後,再進行導航。

十八. VueRouter的滾動行為

VueRouter支援我們自定義切換到一個新的路由時頁面的滾動方式。

比如像下面的這個例子,當我們在同步的路由之間切換時,預設的瀏覽器的下拉框總會定位到上次我們所在的位置。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="./vue/dist/vue.js"></script>
    <script src="./vue-router/dist/vue-router.js"></script>
</head>
<body>
<div id="app">
    <router-view></router-view>
</div>

</body>

<script>
    const home = {
        template: `
      <div>
        <h1>文章:</h1>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <p>學習 vuerouter 的滾動行為</p>
        <router-link to="/detail">點選進入detail</router-link>
    </div>
        `
    }
    const detail = {
        template: `
    <div>
       <h1>this is detail</h1>
       <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
          <p>this is detail</p>
       <router-link to="/">點選進入首頁</router-link>
     </div>`
    }
    const router = new VueRouter({
        routes: [
            {
                path: "/",
                component: home
            },
            {
                path: "/detail",
                component: detail
            }

        ]
    })
    var app = new Vue({
        el: "#app",
        router,
        data: {}
    })
</script>
</html>

Vue-Router支援我們定製這個滾動的集體位置。

定製這個滾動的具體位置需要藉助一個VueRouter的函式:scrollBehavior

const router = new VueRouter({
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
    // return 期望滾動到哪個的位置
    return { x: number, y: number }

    // (offset 只在 2.6.0+ 支援)
		return { selector: string, offset? : { x: number, y: number }}

		// 返回按下前進/後退按鈕時,瀏覽器記錄下的原生狀態
		return savedPosition
  }
})

如果頁面中有錨點,也能像下面這樣模擬hash錨點定位

scrollBehavior (to, from, savedPosition) {
  if (to.hash) {
    return {
      selector: to.hash
    }
  }
}

當然也支援非同步滾動

scrollBehavior (to, from, savedPosition) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({ x: 0, y: 0 })
    }, 500)
  })
}

相關文章