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;
发布了51 篇原创文章 · 获赞 13 · 访问量 3055

猜你喜欢

转载自blog.csdn.net/Sheng_zhenzhen/article/details/104661672