Vue-router外掛使用

雲崖先生 發表於 2020-11-21
Vue

單頁面原理

   Vue是單頁面開發,即頁面不重新整理。

   頁面不重新整理,而又要根據使用者選擇完成內容的更新該怎麼做?Vue中採用錨點來完成。

   如訪問http://127.0.0.1#/index就是主頁,而訪問http://127.0.0.1#/home就是家目錄。

   手動分析url組成與處理檢視的切換非常麻煩,所以Vue提供外掛Vue-router,它能夠根據使用者在位址列中輸入的不同連結展示不同的內容,十分的方便。

Vue-router

   開啟Vue.js官網,在生態系統中找到vue-router

   image-20201117151741748

   然後根據它的官方文件進行安裝即可:

   image-20201117152118071

   文件連結

示例演示

   使用<router-view>當作元件的顯示容器。

   使用<router-link>與屬性toVue切換顯示的元件。

   注意檢視下圖中url的變化,它確實沒有重新整理。

   vuerouter

<body>

<div id="app">
    <router-view></router-view>
</div>

<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
    // 第一步:書寫元件
    const index = {
        template: `
        <div>
            <h1>歡迎訪問主頁</h1>
            <router-link to="/settings">設定</router-link>
            <router-link to="/backend">後臺</router-link>
        </div>
        `
    }

    const backend = {
        template: `
        <div>
            <h1>歡迎訪問後臺</h1>
            <router-link to="/">訪問主頁</router-link>
        </div>
        `
    }

    const settings = {
        template: `
        <div>
            <h1>歡迎訪問個人設定頁面</h1>
            <router-link to="/">訪問主頁</router-link>
        </div>
        `
    }

    // 第二步:配置路由
    // 根據資源請求來展示或隱藏對應的元件
    const routes = [
        // 主頁,/
        {path: "/", component: index},
        {path: "/backend", component: backend},
        {path: "/settings", component: settings},
    ]

    // 第三步:例項化路由物件
    // {routes:routes}
    const router = new VueRouter({routes,})

    // 第四步:根元件中新增路由資訊
    // {router:router}
    const app = new Vue({
        el: "#app",
        router,
    })
</script>
</body>

引數相關

引數傳遞

   在很多情況下,我們都需要使用者的請求地址攜帶一些引數,然後通過這個引數再進行查詢。

   比如查詢某一本書時,位址列通常是這樣的:

#/book/2

   而且很有可能在今後的<router-link>以及methods中都要使用這個引數,如何操作?

{path: "/book/:id(\\d+)", component: book},  // 轉義 \d+

我該如何從Template裡拿到引數:{{ $route.params }}

我該如何從Methods裡拿到引數:this.$route.params

   示例如下:

   vuerouter引數

<body>

<div id="app">
    <router-view></router-view>
</div>

<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
    // 第一步:書寫元件
    const index = {
        template: `
          <div>
              <h1>主頁</h1>
              <input type="text" v-model="book_id" placeholder="輸入要查詢的圖書編號">
              <router-link :to="'/book/'+book_id">查詢圖書</router-link>
          </div>
        `,
        data() {
            return {
                book_id: 0,
            };
        }
    }

    const book = {
        // 在模板以及方法中,你都可以拿到查詢的引數
        template: `
          <div>
              <p>我該如何從Template裡拿到引數:{{ $route.params }}</p>
              <p>我該如何從Methods裡拿到引數:{{ show() }}</p>
              <p v-for="item in books" v-if="item.id==$route.params.id">你查詢的圖書是:{{ item }}</p>
          </div>
        `,
        data() {
            return {
                books: [
                    {id: 1, name: "紅樓夢", author: "曹雪芹", price: 199,},
                    {id: 2, name: "西遊記", author: "吳承恩", price: 179,},
                    {id: 3, name: "三國演義", author: "羅貫中", price: 158,},
                    {id: 4, name: "水滸傳", author: "施耐庵", price: 128,},
                ]
            }
        },
        methods:{
            show(){
                return this.$route.params
            }
        }
    }

    // 第二步:配置路由
    const routes = [
        // 主頁,/
        {path: "/", component: index},
        {path: "/book/:id(\\d+)", component: book},  // 轉義 \d+
    ]

    // 第三步:例項化路由物件
    // {routes:routes}
    const router = new VueRouter({routes,})

    // 第四步:根元件中新增路由資訊
    // {router:router}
    const app = new Vue({
        el: "#app",
        router,
    })
</script>
</body>

預設引數

   根據RestAPI規範,如果沒有攜帶引數則代表查詢所有。

   如果按照上面的示例,直接不帶引數進行請求路由將匹配不上,所以我們還需要做一個查詢所有的功能。

   其實對上面路由進行改進即可,設定為預設引數:

{path: "/book/:id?", component: book},  // ?代表可以有也可以沒有

   示例如下:

   vuerouter預設引數

<body>

<div id="app">
    <router-view></router-view>
</div>

<!--查詢模板-->
<template id="result">
    <div>
        <table border="1" :style="{borderCollapse:'collapse'}">
            <caption :style="{border:'1px solid #000',fontSize:'1.2rem'}">查詢結果</caption>
            <thead>
            <tr>
                <th>編號</th>
                <th>名稱</th>
                <th>作者</th>
                <th>價格</th>
            </tr>
            </thead>
            
            <!-- 查詢一本 -->
            <tbody v-if="$route.params.id">
            <tr v-for="item in books" v-if="item.id == $route.params.id">
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
                <td>{{ item.author }}</td>
                <td>{{ item.price }}</td>
            </tr>
            </tbody>
            
            <!-- 查詢所有 -->
            <tbody v-else>
            <tr v-for="item in books">
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
                <td>{{ item.author }}</td>
                <td>{{ item.price }}</td>
            </tr>
            </tbody>
        </table>
    </div>
</template>

<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
    // 第一步:書寫元件
    const index = {
        template: `
          <div>
          <h1>主頁</h1>
          <input type="text" v-model="book_id" placeholder="輸入要查詢的圖書編號">
          <router-link :to="'/book/'+book_id">查詢單個圖書</router-link>
          <router-link to="/book/">查詢所有圖書</router-link>
          </div>
        `,
        data() {
            return {
                book_id: 0,
            };
        }
    }

    const book = {
        // 在模板以及方法中,你都可以拿到查詢的引數
        template: `#result`,
        data() {
            return {
                books: [
                    {id: 1, name: "紅樓夢", author: "曹雪芹", price: 199,},
                    {id: 2, name: "西遊記", author: "吳承恩", price: 179,},
                    {id: 3, name: "三國演義", author: "羅貫中", price: 158,},
                    {id: 4, name: "水滸傳", author: "施耐庵", price: 128,},
                ]
            }
        },
    }

    // 第二步:配置路由
    const routes = [
        // 主頁,/
        {path: "/", component: index},
        {path: "/book/:id?", component: book},  // 轉義 \d+,?可以有也可以沒有
    ]

    // 第三步:例項化路由物件
    // {routes:routes}
    const router = new VueRouter({routes,})

    // 第四步:根元件中新增路由資訊
    // {router:router}
    const app = new Vue({
        el: "#app",
        router,
    })
    
</script>
</body>

路由別名

路由name

   為每一個路由匹配規則新增name屬性,使其更方便的在模板以及Js程式碼中進行跳轉:

   如下所示:

    const routes = [
        {path: "/", component: index, name: "index"},
        {path: "/book/:id?", component: book, name: "query_book"},
    ]

router-link

   模板中使用router-link與路由的別名name進行跳轉時,格式如下:

<!-- 有引數就傳遞,沒有就不傳遞。注意在to前新增: -->
<router-link :to="{name:'query_book',params:{id:書籍編號}}">查詢</router-link>

$route.push

   Js程式碼中使用跳轉時要用到this.$router.push()這個方法,如下所示:

// 模板中的按鈕
<button @click="func(書籍編號)">檢視詳情</button>

// js
func(bookId){
	// 使用url拼接跳轉
    let url = {path:"/book/"+bookId};
    // 使用別名進行跳轉跳轉:
    // {name:'book_query',params:{id:id}}
    this.$router.push(url);  // 使用$router.push(url)進行跳轉
}

檢視佈局

檢視巢狀

   一個大的元件中可以包含很多小的元件。

   如下所示,有一個school元件,在school元件中你可以檢視到當前的teacherclasses

   當然teacherclasses也都是兩個元件。

   換而言之,我在school元件中點選檢視教師或者班級,我並不希望他跳轉到新的頁面而是在當前頁面的其他位置進行顯示其他元件就可以使用路由巢狀。

<div id="app">
	<!-- 第一層,展示學校 -->
    <router-view></router-view>
</div>

# 子元件中的關鍵程式碼
 <router-view></router-view>
 
# 路由中的程式碼,第一層的路由匹配第一層的router-view,第二層的路由就在第二層的router-view中顯示
 path: "/school", component: school, name: "school", children: [
     {path: "/school/teacher", component: teacher, name: "teacher"},
     {path: "/school/classes", component: classes, name: "classes"},
 ]

   vuerouter巢狀

<body>

<div id="app">
	<!-- 第一層,展示學校 -->
    <router-view></router-view>
</div>


<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>
    // 第一步:書寫元件
    const school = {
        template: `
        <div>
            <h1>歡迎來到大肥羊學校</h1>
            <router-link :to="{name:'classes'}">檢視班級</router-link>
            <router-link :to="{name:'teacher'}">檢視老師</router-link>
            <!-- 第二層,展示班級或者教師 -->
            <router-view></router-view>
        </div>
        `
    }

    const teacher = {
        template: `
          <div>
            <ul>
              <li v-for="item in teacherMessage">{{item.id}}-{{item.name}}</li>
            </ul>
          </div>
        `,
        data() {
            return {
                teacherMessage: [
                    {id: 1, name: "王老師",},
                    {id: 2, name: "張老師",},
                    {id: 3, name: "李老師",},
                ]
            }
        }
    }


    const classes = {
        template: `
          <div>
              <ul>
                <li v-for="item in classMessage">{{item.id}}-{{item.name}}</li>
              </ul>
          </div>
        `,
        data() {
            return {
                classMessage: [
                    {id: 1, name: "一年級一班",},
                    {id: 2, name: "一年級二班",},
                    {id: 3, name: "一年級三班",},
                ]
            }
        }
    }

    // 第二步:配置路由
    const routes = [
        {
            path: "/school", component: school, name: "school", children: [
                {path: "/school/teacher", component: teacher, name: "teacher"},
                {path: "/school/classes", component: classes, name: "classes"},
            ]
        },
    ]

    // 第三步:例項化路由物件
    // {routes:routes}
    const router = new VueRouter({routes,})

    // 第四步:根元件中新增路由資訊
    // {router:router}
    const app = new Vue({
        el: "#app",
        router,
    })

</script>
</body>

巢狀的問題

   當頁面發生變化,如#school/classes跳轉到#school/teacherschool元件將會產生複用。

   這代表school的元件宣告週期鉤子函式不會被重複呼叫,就可能造成資料更新不及時的問題。

   舉一個例子,上述示例中的school歡迎語是歡迎來到大肥羊學校,如果它是鉤子函式created()從後端獲取的資料,在使用者檢視#school/classes後跳轉到#school/teacher這個時間點中間後端資料發生了改變,變成了歡迎來到小肥羊學校,由於元件複用問題不會再次執行created(),則代表使用者依舊看到的是歡迎來到大肥羊學校。如下所示,我們只有手動更新標語才能執行更新,這顯然是不符合常理的:

   vuerouter巢狀問題

<body>

<div id="app">
    <router-view></router-view>
</div>

<script src="./vue.js"></script>
<script src="./vue-router.js"></script>
<script>

    // 假設是從後端抓取資料
    let schoolTitle = "歡迎來到大肥羊學校";

    // 5s後發生改變
    setTimeout(() => {
        schoolTitle = "歡迎來到小肥羊學校";
    }, 5000);

    // 第一步:書寫元件
    const school = {
        template: `
        <div>
            <h1>{{ title }}</h1>
            <router-link :to="{name:'classes'}">檢視班級</router-link>
            <router-link :to="{name:'teacher'}">檢視老師</router-link>
            <p><button @click="updateTitle">更新新的標語</button></p>
            <router-view></router-view>
        </div>
        `,
        data(){
            return {
                title : "",
            }
        },
        created(){
            // 假設傳送非同步請求
            console.log("school鉤子函式觸發了...")
            this.title = schoolTitle;
        },
        methods:{
            updateTitle(){
                this.title = schoolTitle;
            }
        }
    }

    const teacher = {
        template: `
          <div>
            <h3>老師太多了,顯示不過來...</h3>
          </div>
        `,
    }


    const classes = {
        template: `
          <div>
            <h3>班級太多了,顯示不過來...</h3>
          </div>
        `,
    }

    // 第二步:配置路由
    const routes = [
        {
            path: "/school", component: school, name: "school", children: [
                {path: "/school/teacher", component: teacher, name: "teacher"},
                {path: "/school/classes", component: classes, name: "classes"},
            ]
        },
    ]

    // 第三步:例項化路由物件
    // {routes:routes}
    const router = new VueRouter({routes,})

    // 第四步:根元件中新增路由資訊
    // {router:router}
    const app = new Vue({
        el: "#app",
        router,
    })

</script>
</body>

解決問題

   如果想解決元件複用鉤子函式不執行的問題,我們可以使用watch來監聽$route物件,也就是使用watch來監聽位址列變化,當發生變化時就重新獲取資料。

   或者使用 2.2 中引入的 beforeRouteUpdate 導航守衛,解決思路如下圖所示:

   image-20201117180236494

   vuerouter巢狀問題解決

   程式碼如下,使用watch進行解決:

    const school = {
		template:"...",
        data(){
            return {
                title : "",
            }
        },
        created(){
            // 假設傳送非同步請求
            this.getTitle();
        },
        methods:{
            getTitle(){
                // 從後端獲取資料
                this.title = schoolTitle;
            }
        },
        watch:{
            $route(to,from){
                // to 要跳轉的頁面
                // from 從那個頁面進行跳轉
                this.getTitle();
            }
        }
    }

   使用導航守衛進行解決的程式碼如下:

    // 第一步:書寫元件
    const school = {
    	template:"...",
        data(){
            return {
                title : "",
            }
        },
        created(){
            // 假設傳送非同步請求
            this.getTitle();
        },
        methods:{
            getTitle(){
                // 從後端獲取資料
                this.title = schoolTitle;
            }
        },
        beforeRouteUpdate (to, from, next) {
            this.getTitle();
        }
    }

命名檢視

   命名檢視就是說可以在一個頁面上,使用多個<router-view>,相較於路由巢狀的層級關係,它是一種扁平化的設計。

   如,頭部導航欄,左側選單欄,右邊內容塊三個元件,都顯示在一個頁面上,就可以使用命名檢視。

   核心程式碼如下:

# 根元件模板
<div id="app">
    <!-- 這裡只放個人主頁 -->
    <router-view></router-view>
    <router-view name="menu"></router-view>
    <router-view name="show"></router-view>
</div>
   

# Js配置路由,/代表根目錄。有三個檢視,router-view
path: "/", components: {
    default: header,  // 如果 view-router沒有name屬性,則用default
    menu: menu,
    show: show,
}

   image-20201117194355773

 <style>
        *{
            padding: 0;
            margin: 0;
            box-sizing: border-box;
        }
        header{
            height: 45px;
            display: flex;
            justify-content: space-evenly;
            background-color: #ddd;
            align-items: center;
        }
        body>div>div:nth-child(2){
            display: inline-flex;
            width: 15%;
            border: 1px solid #ddd;
            height: 1000px;
        }
        menu>ul{
            list-style-type: none;
            display: inline-flex;
            flex-flow: column;
            
        }
        menu>ul>li{
            margin: 10px 0 0 10px;
        }
        body>div>div:last-child{
            display: inline-flex;
            justify-content: center;
            border: 1px solid #ddd;
            width: 70%;
            height: 1000px;
        }

    </style>

<body>
    <div id="app">
        <!-- 這裡只放個人主頁 -->
        <router-view></router-view>
        <router-view name="menu"></router-view>
        <router-view name="show"></router-view>
    </div>
   
    <!-- 頭部元件 -->
    <template id="header">
        <div>
            <header><span>首頁</span><span>新聞</span><span>關注</span><span>連結</span></header>
        </div>
    </template>

    <!-- 左側選單 -->
    <template id="menu">
        <div>
            <menu>
                <ul>
                    <li>最新</li>
                    <li>最熱</li>
                    <li>最多評論</li>
                </ul>
            </menu>
        </div>
    </template>

    <!-- 內容區域 -->
    <template id="show">
        <div>
            <section>
                <h1>內容區域</h1>
            </section>
        </div>
    </template>

    <script src="vue.js"></script>
    <script src="vue-router.js"></script>
    <script>

        // 第一步:書寫元件
        const header = {
            template: "#header",
        }

        const menu = {
            template: "#menu",
        }

        const show = {
            template: "#show",
        }

        // 第二步:配置路由
        const routes = [
            {
                // 當你訪問主頁時,有三個元件扁平顯示
                path: "/", components: {
                    default: header,  // 如果 view-router沒有name屬性,則用default
                    menu: menu,
                    show: show,
                }
            }

        ]

        // 第三步:例項化路由物件
        const router = new VueRouter({
            routes, // es6新語法
        })


        // 第四步:根元件中新增路由資訊 
        const app = new Vue({
            el: "#app",
            router,
        });

    </script>
</body>

重定向

redirect

   當你訪問一個頁面時,可以重定向至另一個頁面,如下示例,使用redirect進行重定向。

   訪問/doc,重定向到/help中。但是位址列中顯示的還是/help

const routes = [
    // 當使用者訪問doc時,將會跳轉到help中,位址列中顯示是help
    { path: "/help", component: help, name: "help"},
    { path: "/doc", redirect: { name: "help" } }
]

alias

   如果你使用alias引數進行匹配,就方便許多了,並且位址列中顯示的是使用者輸入的值,但是當輸入的路徑不存在,則不會顯示:

   Vue的路由重定向alias引數

const routes = [
    // 使用者輸入在alias中的所有路徑,都會交給help元件進行處理
    { path: "/help", component: help, name: "help", alias: ["/doc", "/doc.html", "/help.html"] },
]

history模式

   如果你的url中不想有#號的錨點,可開啟history模式。

   同時你還需要在後端做相應的配置,參見官方文件:

   點我

切換動畫

   相信現在你已經對單頁面開發有所瞭解,單頁面開發說白了就是根據url請求的#後的引數不停的更換要顯示的元件。

   所以我們可以為這一切換過程加上過渡動畫,你可以在其他子元件模板中新增<transition>標籤,並自己書寫css類或者引用第三方庫。

   如下所示:

   Vue的路由動畫

   我這裡是單獨給每個子元件加的動畫:

        // 書寫元件
        const index = {
            template:
             `
            <transition enter-active-class="animate__animated  animate__bounce">
                <h1>wecome to index</h1>
            </transition>
            `,

        }

        const backend = {
            template:             `
            <transition enter-active-class="animate__animated  animate__bounce">
                <h1>wecome to backend</h1>
            </transition>
            `,
        }

   如想了解更多,請參考官方文件。