VueRouter路由(Vue全家桶之一詳解)

Vue路由初識

  • 兩種模式: hash 模式或HTML5 歷史模式
  • vue-router 默認 hash 模式 —— 使用 URL 的 hash 來模擬一個完整的 URL,於是當 URL 改變時,頁面不會重新加載。
  • 如果不想要很醜的 hash,我們可以用路由的 history 模式,這種模式充分利用 history.pushState API 來完成 URL 跳轉而無須重新加載頁面。

Vuehash路由模擬

hash路由基礎.jpg

Vue-histroy路由模擬

在這裏插入圖片描述

vue-router 使用初識

路由:一個路徑對應一個組件
1.創建組件
2.配置路由映射表
3.註冊路由映射表
4. 把路由掛載到根實例上;

<body>
    <div id="app">
        <!-- to : 跳轉的路由  tag:指定router-link渲染標籤 -->
        <!-- 使用 router-link 組件來導航. -->
        <!-- 通過傳入 `to` 屬性指定鏈接.  tag:指令router-link渲染成啥標籤-->
        <!-- <router-link> 默認會被渲染成一個 `<a>` 標籤 -->
        <router-link to="/allhome" tag="button">首頁</router-link>
        <router-link to="/allperson" tag="button">個人中心</router-link>
        
        <!-- 用於顯示路由對應組件的地方 -->
        <!-- 根據路由顯示對應的組件 -->
        <!-- 路由出口 -->
        <!-- 路由匹配到的組件將渲染在這裏 -->
        <router-view></router-view>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        // 路由:一個路徑對應一個組件
        // 1.創建組件    
        // 2.配置路由映射表
        // 3.註冊路由映射表
        // 4. 把路由掛載到根實例上; 
        let home = {
            data() {
                return {
                    con: '首頁'
                }
            },
            template: "<div>{{con}}</div>"
        }
        let person = {
            data() {
                return {
                    con: '個人中心頁'
                }
            },
            template: "<div>{{con}}</div>"
        }
        // 配置路由映射表: 是路由和組件的配對情況
        let routes = [{
            path: "/allhome",
            component: home
        }, {
            path: "/allperson",
            component: person
        }];
        // 註冊路由映射表
        let router = new VueRouter({
            routes: routes // routes 屬性名不可以改
        })
        // 將路由掛載到根實例上
        let vm = new Vue({
            el: "#app",
            data: {

            },
            components: {
                home,
                person
            },
            router
        })
    </script>
</body>

VueRouter中的方法

引入時:vue-router一定要放vue.js的後面
當切換組件時,組件會銷燬;

當每一個被路由渲染出來的組件上有一個$router屬性,在這個屬性的原型上有一些操作路由的方法
1.push : 直接跳轉到當前路徑對應的路由上 push(路徑)
2.back : 回退到上一次的路由上
3.go(number):可以往前可以往後回退後前進幾個
原生:
window.history
window.history.go(-1)

<body>
    <div id="app">
        <!-- to屬性 -->
        <router-link to="/home" tag="button">首頁</router-link>
        <router-link to="/list" tag="button">列表頁</router-link>
        <!-- 展示對應的組件,當組件切換時,組件的DOM元素刪除 -->
        <router-view></router-view>

    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        //window.history  
        //window.history.go(-1) 

        // vue-router一定要放vue.js的後面
        // 當切換組件時,組件會銷燬;
        // 當每一個被路由渲染出來的組件上有一個$router屬性,在這個屬性的原型上有一些操作路由的方法
        // 1.push : 直接跳轉到當前路徑對應的路由上 push(路徑)
        // 2.back : 回退到上一次的路由上
        // 3.go(number):可以往前可以往後回退後前進幾個
        let home = {
            data() {
                return {}
            },
            created() {
                // 顯示組件時,需要再次創建組件實例;調用鉤子函數
                console.log("創建首頁");
            },
            methods: {
                goList() {
                    console.log(this);
                    this.$router.push("/list")

                }
            },
            template: "<div>首頁內容<button @click='goList'>去列表頁</button></div>",
            beforeDestroy() {
                console.log("銷燬首頁");
            },
        };
        let list = {
            data() {
                return {}
            },
            methods: {
                goBack() {
                    console.log(this);
                    // this.$router.back("/home")
                    this.$router.go(-1)

                }
            },
            template: "<div>列表頁<button @click='goBack'>返回</button></div>"
        };
        // 路由映射表
        let routes = [{
            path: "/home",
            component: home
        }, {
            path: "/list",
            component: list
        }];
        // 註冊路由映射表
        let router = new VueRouter({
            routes
        });
        // 掛載到根實例上
        let vm = new Vue({
            el: "#app",
            router
        });
    </script>
</body>

路由的嵌套

在組件路由配置是,對象中有children屬性,屬性值是一個數組,裏面配置了子路由,子路由中不需要加父路由路徑地址,同時也不需要加"/",當子路由進行匹配式,會自動加上父路由和/到子路由的前面;
子路由path後面也可以從一級路由到耳機路由
二級路由不能直接配置到routes,應該找到它對應的以及路由,配置到其children屬性上;

下面例子:detail 和login都是list組件的子路由組件

<body>
    <div id="app">
        <router-link to="/home" tag="button" class="a">首頁</router-link>
        <router-link to="/list" tag="button" class="b">列表頁</router-link>
        <router-view></router-view>
    </div>
    <template id="list">
        <div>
            列表頁
            <router-link to="/list/detail">詳情頁</router-link>
            <router-link to="/list/login">登錄頁</router-link>
            <router-view></router-view>
        </div>
    </template>  
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        let home={
            template:"<div>首頁</div>"
        }
        let list={
            template:"#list"
        };
        let detail = {
            template:"<div>詳情頁</div>"
        }
        let login = {
            template:"<div>登錄註冊頁</div>"
        };
        // detail 和login都是list組件的子路由組件
        // 在組件路由配置是,對象中有children屬性,屬性值是一個數組,裏面配置了子路由,
路由中不需要加父路由路徑地址,同時也不需要加"/",當子路由進行匹配式,會自動加上父路由和/到子路由的前面;
        // 二級路由不能直接配置到routes,應該找到它對應的以及路由,配置到其children屬性上;
        let routes  =[
            {path:"/home",component:home},
            {path:"/list",component:list,children:[
                {path:"detail",component:detail},
                {path:"login",component:login}
            ]}
        ];
        let router = new VueRouter({
            routes
        })
        let vm = new Vue({
            el:"#app",
            data:{

            },
            router
        })
    
    </script>
</body>

命名路由

將to改成動態屬性 :to={name:組件的name名稱}
通過名字取匹配路由

<body>
    <div id="app">
        <!-- 將to改成動態屬性 :to={name:組件的name名稱} -->
        <router-link :to="{name:'first'}">首頁</router-link>
        <router-view></router-view>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        // 通過名字取匹配路由
        let home={
            template:"<div>首頁</div>"
        }
        let routes  =[
            {path:"/home",component:home,name:"first"}
        ];
        let router = new VueRouter({
            routes
        })
        let vm = new Vue({
            el:"#app",
            data:{

            },
            router
        })
    
    </script>
</body>

動態路由

動態路由:路由傳參;路徑後面是一個 :變量 這就是動態路由,也可以叫路由動態傳參;會把id以屬性方式放到$route的params屬性上,屬性值就是路由實際的路徑值

1.代碼量少
2. 由於動態路由渲染的是同一個home組件,所以home組件不再銷燬,當然也不再創建,複用了之前的組件,性能高;但是生命週期的前四個鉤子函數也不再執行;

動態路由.png

<body>
    <div id="app">
        <router-link to="/home/1">第一本</router-link>
        <router-link to="/home/2">第二本</router-link>
        <router-link to="/home/3">第三本</router-link>
        <router-view></router-view>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        // $router:push go  back  forward
        // $route: 

        // 路由的動態傳參:
        // 路由傳參有哪些方式;

        let home = {
            // created(){
            //     // 
            //     console.log(this.$route);
            // },
            // watch:{
            //     '$route'(to,from){
            //        console.log(to);// to: 要到達的組件的$route
            //        console.log(from);// from :上一個$route
            //     }
            // },
            // 路由守衛
            // 在路由更新之前會默認調用該鉤子函數
            beforeRouteUpdate(to, from, next) {
                console.log(to); // 即將進入的目標路由的對象信息
                console.log(from); // 即將離開路由的對象信息
                console.log(next); // 函數
                // 1. next 函數
                // next(): 會立即進入到目標路由
                // next(false):中斷當前的導航;不再去訪問下一個路由
                // if(to.params.id==3){
                //     next({path:"/home/1"});
                //     return;
                // }
                // 權限校驗
                next();
            },
            template: "<div>這是我喜歡的第{{$route.params.id}}本書</div>"
        }

        // 動態路由:路由傳參;路徑後面是一個:變量;這就是動態路由,也可以叫路由動態傳參;
會把id以屬性方式放到$route的params屬性上,屬性值就是路由實際的路徑值 

        // 1.代碼量少
        // 2. 由於動態路由渲染的是同一個home組件,所以home組件不再銷燬,當然也不再創建,
複用了之前的組件,性能高;但是生命週期的鉤子函數也不再執行;
        let routes = [{
            path: "/home/:id",
            component: home
        }];
        let router = new VueRouter({
            routes
        })
        let vm = new Vue({
            el: "#app",
            data: {},
            router
        })
    </script>
</body>

路由傳參

  • 路由傳參有哪些方式;
  1. id 動態路由傳參;路徑後面是一個:變量;這就是動態路由,也可以叫路由動態傳參;會把id以屬性方式放到$route的params屬性上,屬性值就是路由實際的路徑值
  2. query傳參 path
  3. params傳參 name 命名路由

query和params.png

<body>
    <div id="app">
        <router-link :to="{name:'first'}">首頁</router-link>
        <router-link :to="{name:'second'}">列表頁</router-link>
        <router-view></router-view>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        // 通過名字取匹配路由 
        // 1. id 動態路由傳參;路徑後面是一個:變量;這就是動態路由,也可以叫路由動態傳參;
      會把id以屬性方式放到$route的params屬性上,屬性值就是路由實際的路徑值 
        // 2.query傳參 path
        // 3.params傳參 name
        let home = {
            methods: {
                goList() {
                    // this.$router.push({
                    //     path: "/list",
                    //     query: {
                    //         id: 100
                    //     }
                    // }),
                    this.$router.push({
                        name: "second",
                        params: {
                            id: 500
                        }
                    })
                }
            },
            template: "<div>首頁 <button @click='goList'>1去列表</button></div>"
        }
        let list = {
            created() {
                // console.log(this.$route)
                // let id = this.$route.query.id;
                // console.log(id);
                console.log(this.$route);
                let id = this.$route.params.id;
                console.log(id);
            },
            template: "<div>列表頁</div>"
        }
        let routes = [{
                path: "/home",
                component: home,
                name: "first"
            },
            {
                path: "/list",
                component: list,
                name: "second"
            }

        ];
        let router = new VueRouter({
            routes
        })
        let vm = new Vue({
            el: "#app",
            data: {

            },
            router
        })
    </script>
</body>

命名視圖

沒有name屬性,會顯示屬性爲default的組件
let routes =[
{path:"/home",components:{
default:home,// home對應沒有name屬性的router-view
// 這個對象屬性名和router-view的name屬性值對應
b:foo,
c:bar
},name:“first”}
];

<body>
    <div id="app">
        <!-- 將to改成動態屬性 :to={name:組件的name名稱} -->
        <router-link :to="{name:'first'}">首頁</router-link>
        <!-- 沒有name屬性,會顯示屬性爲default的組件 -->
        
        <router-view></router-view>
        <router-view name="b"></router-view>
        <router-view name="c"></router-view>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        let home={
            template:"<div>首頁</div>"
        }
        let foo = {
            template:"<div>foo</div>"
        }
        let bar={
            template:"<div>bar</div>"
        }
        let routes  =[
            {path:"/home",components:{
                default:home,// home對應沒有name屬性的router-view
                // 這個對象屬性名和router-view的name屬性值對應
                b:foo,
                c:bar
            },name:"first"}
        ];
        let router = new VueRouter({
            routes
        })
        let vm = new Vue({
            el:"#app",
            data:{

            },
            router
        })
    </script>
</body>

redirect: 路由地址重定向

redirect: 重定向

  1. routes:[{path:"/a",redirect:"/home"}]
  2. routes:[{path:"/a",redirect:{name:“路由名字”}}]
  • 最後匹配不到重定向到首頁或者404頁面
    path.jpg
<body>
    <div id="app">
        <router-link to="/home" tag="button">首頁</router-link>
        <router-link to="/list" tag="button">列表頁</router-link>
        <router-view></router-view>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        
        // 路由{path:"",component:"",children:[{path:"",component:""},
{path:"",component:""}]}
        let home = {
            template: "<div>首頁內容</div>"
        };
        let list = {
            template: "<div>列表頁內容</div>"
        };
        let found = {
            template: "<div>頁面404</div>"
        };
        // redirect: 重定向
        // 1. routes:[{path:"/a",redirect:"/home"}]
        // 2. routes:[{path:"/a",redirect:{name:"路由名字"}}]
        let routes = [{
            path: "/",
            // component: home
            // 一個地址一個組件,如果想用就重定向,
        }, {
            path: "/home",
            component: home
        }, {
            path: "/list",
            component: list
        }, {
            path: "/a",
            component: found
        }, {
            path: "*",
            redirect: "/home"
        }];
        let router = new VueRouter({
            routes
        });
        let vm = new Vue({
            el: "#app",
            router
        })
    </script>
</body>

路由的守衛(七個鉤子函數)

導航守衛:當切換導航時,會默認調用一些鉤子函數,那麼這些鉤子函數就是導航的守衛;可以在進入這個導航或者離開這個導航時,在鉤子函數中做一些事情
守衛: 7個
全局守衛: beforeEach afterEach beforeResolve
路由獨享守衛: beforeEnter
組件內部守衛: beforeRouteEnter beforeRouteUpdate beforeRouteLeave

<body>
    <div id="app">
        <router-link to="/home/1" tag="button">第一本</router-link>
        <router-link to="/home/2" tag="button">第二本</router-link>
        <router-link to="/home/3" tag="button">第三本</router-link>
        <router-link to="/list" tag="button">列表</router-link>
        <router-view></router-view>
    </div>

    <!--  IMPORT JS -->
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script src="../node_modules/vue-router/dist/vue-router.js"></script>
    <script>
        //切換到另一個組件,組件時會銷燬的;
        // 導航守衛:當切換導航時,會默認調用一些鉤子函數,那麼這些鉤子函數就是導航的守衛;
      可以在進入這個導航或者離開這個導航時,在鉤子函數中做一些事情
        // 生命週期 11個
        // 守衛: 7個
        // 全局守衛: beforeEach  afterEach  beforeResolve
        // 路由獨享守衛: beforeEnter 
        // 組件內部守衛: beforeRouteEnter beforeRouteUpdate beforeRouteLeave

        let home = {
            beforeDestroy() {
                // console.log("銷燬");
            },
            template: "<div>這是第{{$route.params.id}}本</div>",
            beforeRouteEnter(to, from, next) {
                // 這個鉤子函數執行時進入組件實例之前,此時組件實例還沒有創建;
                // console.log(this); //this->window
                console.log("組件內的守衛-home-beforeRouteEnter");
                next(vm => {
                    // 最後執行
                    // 當next執行傳入回調函數,回調函數不能立即執行,等到組件實例創建好之後,
                  纔會觸發這個回調函數;其中vm就是組件實例
                    //console.log(vm);

                })
            },
            beforeRouteUpdate(to, from, next) {
                // 當複用這個組件並且更新了組件時,這個函數纔會被調用
                console.log("組件內的守衛-home-beforeRouteUpdate");
                next();
            },
            beforeRouteLeave(to, from, next) {
                // 當離開list這個組件時,會調用這個鉤子函數
                console.log("組件內的守衛-home-beforeRouteLeave");
                next();

            }
        };

        // 當第一次進入到list組件時,只觸發了beforeRouteEnter
        let list = {
            template: "<div>列表內容</div>",
            // 組件內的守衛
            beforeRouteEnter(to, from, next) {
                // 這個鉤子函數執行時進入組件實例之前,此時組件實例還沒有創建;
                // console.log(this); //this->window
                console.log("組件內的守衛-list-beforeRouteEnter");

                next(vm => {
                    // 當next執行傳入回調函數,回調函數不能立即執行,會等到組件實例創建好之後,
                  纔會觸發這個回調函數,其中vm就是組件實例
                    console.log(vm);

                })
            },
            // beforeRouteUpdate(to,from,next){
            // 當複用這個組件並且更新了組件時,這個函數纔會被調用;
            // this--> 當前的組件實例
            //     console.log("組件內的守衛-list-beforeRouteUpdate");
            //     next()
            // },
            beforeRouteLeave(to, from, next) {
                // 當離開list這個組件時,會調用這個鉤子函數
                console.log("組件內的守衛-list-beforeRouteLeave");
                next();
            }
        };

        let routes = [{
            path: "/home/:id",
            component: home,
            beforeEnter: (to, from, next) => {
                // 路由獨享的守衛
                console.log("home-路由獨享的守衛");
                next()
            }
        }, {
            path: "/list",
            component: list,
            beforeEnter: (to, from, next) => {
                // 路由獨享的守衛
                console.log("list-路由獨享的守衛");
                next()
            }
        }];
        let router = new VueRouter({
            routes
        })
        // 全局的前置鉤子函數;只要切換組件,就會執行
        router.beforeEach((t0, form, next) => {
            console.log("beforeEach-全局前置的鉤子函數");
            // console.log(to);// 到哪去
            // console.log(from);// 從哪來
            // console.log(1);
            // 在這個鉤子函數中獲取到用戶的信息,進行權限的校驗,如果不符合要求,
          那麼next不需要運行;或者直接跳轉到首頁或 403頁面
            next(); // 只有執行了next,纔會往下繼續跳轉路由;
        })
        // 全局解析守衛
        router.beforeResolve((t0, form, next) => {
            console.log("beforeResolve-全局解析守衛");
            // console.log(to);// 到哪去
            // console.log(from);// 從哪來
            // console.log(2);
            next(); // 只有執行了next,纔會往下繼續跳轉路由;
        })
        // 全局後置的鉤子函數
        router.afterEach((t0, form) => {
            // 路由切換成功以後執行的鉤子函數
            console.log("afterEach-全局後置的鉤子函數");
            // console.log(to);
            // console.log(3);
        })

        // 用戶  /list   管理員 : /list   /edit  /home
        // 切換路由時:路由守衛執行順序beforeEach==>beforeEnter==>beforeRouteUpdate==>
beforeResolve==>afterEach
        //當進入組件時,先觸發全局的前置鉤子,然後觸發進入組件的路由獨享守衛,
然後觸發組件內部的beforeRouteEnter,最後觸發全局的beforeResolve和全局後置鉤子函數
        let vm = new Vue({
            el: '#app',
            router
        });

        // 從第三本到列表
        // 組件內的守衛-home-beforeRouteLeave  beforeEach-全局前置的鉤子函數  
list-路由獨享的守衛  組件內的守衛-list-beforeRouteEnter    beforeResolve-全局解析守衛   
afterEach-全局後置的鉤子函數  調用beforeRouteEnter守衛中傳給next的回調函數

        // 從列表到第三本
        // 組件內的守衛-list-beforeRouteLeave  beforeEach-全局前置的鉤子函數 
home-路由獨享的守衛  組件內的守衛-home-beforeRouteEnter    beforeResolve-全局解析守衛  
afterEach-全局後置的鉤子函數  調用beforeRouteEnter守衛中傳給next的回調函數

        // 從第三本到第二本
        // beforeEach-全局前置的鉤子函數   組件內的守衛-list-beforeUpdata     
beforeResolve-全局解析守衛   afterEach-全局後置的鉤子函數  
    </script>
</body>

完整導航解析流程.jpg

VueRouter核心源碼

class VueRouter{
    constructor(options){
        const {routes}=options;
        // 監聽當前頁面的hash值的切換
        // 當第一次解析頁面時,會有一個默認的hash值
        /// 循環遍歷routes,把path和component重新放入一個新的對象中
        // {"/home/:id":home}
        this.routeMap = routes.reduce((prev,next)=>{
            prev[next.path]=next.component;
            return prev;
        },{});
        // 
        // this ==> VueRouter的實例,也是每一個組件上的_router
        Vue.util.defineReactive(this.route={},'path',"/");
        window.addEventListener("load",()=>{
            // 如果沒有hash值,那麼給其賦默認值/;如果本來就有hash,什麼也不做;
            location.hash?null:location.hash="/";
        })
        window.addEventListener("hashchange",()=>{
            // 當頁面hash值發生改變以後,會觸發這個方法;1.a標籤  2.手動
            // 獲取噹噹前頁面的hash值,獲取到#後面的字符串;
            let path = location.hash.slice(1);
            this.route.path = path;
        })
    }
}
//在Vuex注入了$store,在路由注入_router
VueRouter.install=function(_Vue){

    _Vue.mixin({
        // 給每一個組件新增一個_router的屬性,這個屬性的屬性值是VueRouter的實例
        beforeCreate(){
            // this==> 每一個組件實例
            if(this.$options&&this.$options.router){
                // 給每一個組件實例新增_router屬性,屬性值就是VueRouter的實例; 
              這是給Vm這個Vue實例新增
                this._router=this.$options.router;
            }else{
                // 給vm的組件的實例新增
                this._router=this.$parent && this.$parent._router;
            }
            // 給每一個實例添加$route屬性,
            Object.defineProperty(this,"$route",{
                value:{
                    route:this._router.route// ? 這個route
                }
            });

            // 註冊兩個內置組件
            // router-link  router-view
            // 註冊全局組件
            <router-link to="/home"></router-link>
            let child = {}
            Vue.component("router-link",{ 
                props:{
                    to:String
                },
                // template:"<div></div>",
                render(createElement){// h是一個createdElement,這個方法可以直接接受一個組件;
                  createElement 用來創建虛擬的DOM
                    //return createElement("a",{},首頁)
                    // render : 渲染函數
                    // render: 將虛擬DOM可以轉成真實的DOM;這個函數返回什麼,
                  那麼最終router-link就渲染成什麼
                    // this==> 當前的組件實例
                    // return + 組件;可以把組件渲染成一個真實的DOM;
                    // return h(child);
                    // return <child></child>
                    // $slots
                    return <a href={`#${this.to}`}>this.$slots.default</a>
                }
            });
            // router-view : 根據頁面的hash值顯示對應的組件
            Vue.component("router-view",{
                render(createdElement){
                    // 這個傳遞一個動態的組件名字
                    return createElement(this._router.routeMap[this._router.route.path])
                }
            })
        }
    })
};
let router=new VueRouter({
    routes:[]
})
let vm = new Vue({
    router,
    render(){

    }
})
export default VueRouter;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章