美文网首页
Vue现代化使用方法(五)--vue-router

Vue现代化使用方法(五)--vue-router

作者: li4065 | 来源:发表于2018-05-20 10:50 被阅读300次

    vue-router是vue全家桶中,用来控制路由的插件

    安装

    依然使用npm进行安装

    npm install vue-router --save

    基本使用方式

    // 在项目目录建立两个文件pageA.vue和pageB.vue
    // pageA.vue内容如下
    <template>
        <div>
            <p>{{content}}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    content: '这是页面A'
                }
            }
        }
    </script>
    ...
    // pageB.vue内容如下
    <template>
        <div>
            <p>{{content}}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    content: '这是页面B'
                }
            }
        }
    </script>
    ...
    // index.js内容调整如下
    import Vue from 'vue';
    import app from './index.vue';
    import VueRouter from 'vue-router'; // 引入vue-router
    Vue.use(VueRouter); // 引用VueRouter
    
    import pageA from './pageA.vue';
    import pageB from './pageB.vue';
    
    const routes =  [
        { path: '/pageA', component: pageA },
        { path: '/pageB', component: pageB }
    ];
    
    const router = new VueRouter({
        routes // (简写)相当于 routes: routes,所以这个变量名在简写下是固定的不可修改,如果用key:value的形式,key值保持这个名词,value对应的变量名可变(routes: newName)
    });
    
    let vm = new Vue({
        el: "#app",
        router,
        render: h => h(app) 
    });
    ...
    // index.vue内容调整如下
    // 因为我们在index.js中设置render渲染区域是index.vue,所以在index.vue中router-view针对是当前组件
    // 如果你在index.js的初始化函数中设定render: h => h('router-view'),同时在routes中设置一个默认展示{ path: '/', component: app },那么此时index.vue中的router-view是无效
    // to="/pageA"和to="pageA"跳转结果是不一致的,'/pageA'是在路由的根目录替换路由,'pageA'是在当前路由添加路由
    <template>
        <div class="wrap">
            <div class="title">
                <router-link to="/pageA">Go to pageA</router-link>
                <router-link to="/pageB">Go to pageB</router-link>
            </div>
            <div class="content">
                <router-view></router-view>
            </div>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
    
                }
            },
            computed: {
    
            },
            methods: {
    
            },
            mounted () {
                 console.log(this.$router); // 在组件内可以使用this.$router访问路由信息
            },
            created () {
    
            },
            components: {
    
            }
        }
    </script>
    

    router-link: 是设定路由的导航区域

    // 基本参数
    to: 路由的指向地址,点击时,默认会把to中参数传给router.push()方法,实现页面跳转
    replace: 修改默认跳转的方法,把默认的router.push()替换为router.replace(),这样页面在跳转时,不会有历史记录 
    tag: router-link默认会渲染为a标签,使用这个参数可以指定渲染为何标签,而且一样会监听点击事件
    

    router-view: 设定路由内容展示的区域

    路由传参

    在实际开发中,页面通过URL获取一定参数是比较常见的需求,vue-router可以在routes中设定这些信息

    // index.js添加一个可以接收相关参数的匹配
    const routes =  [
        { path: '/pageA', component: pageA },
        { path: '/pageA/:id', component: pageA },
        { path: '/pageB', component: pageB }
    ];
    ...
    // index.vue添加相关参数
    <div class="title">
        <router-link to="/pageA">Go to pageA</router-link>
        <router-link to="/pageA/900864">Go to pageA ID</router-link>
        <router-link to="/pageB">Go to pageB</router-link>
    </div>
    ...
    // pageA.vue添加对这个参数的处理
    <div>
        <p>{{content}} {{ $route.params.id }}</p>
    </div>
    ...
    // 在pageA.vue的mounted钩子函数中,查看相关传参数
    // this.$route.params输出的就是参数的对象形式,{id: '900864'}
    mounted () {
        console.log(this.$route.params);
    }
    ...
    // params这个信息只在路由匹配时,才会有输出,默认没有值
    // 比如这个例子中的pageA和接收参数的pageA,其实调用是同一个组件,当处在不同的URL下,只有在到了含参数的pageA链接时,mounted才会有输出
    // 此时你应该留意到了一个问题,就是我们在对pageA和包含id的pageA进行切换时,mounted这个钩子函数只会执行一次
    // 那是因为vue处在这种情况下会尽可能的复用当前组件,并不会销毁重建,所以钩子函数只执行一次
    // 如果想要对这种变化进行监听,可以使用beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave这几个钩子函数来进行监听
    // 代码做如下改造,next方法是必要的,保证路由可以正常调用
    // to是要进入路由的信息,from是离开的路由信息
    beforeRouteEnter (to, from, next) {
        console.log('beforeRouteEnter');
        console.log(to);
        console.log(from);
        next();
    },
    beforeRouteUpdate (to, from, next) {
        console.log('beforeRouteUpdate');
        console.log(to);
        console.log(from);
        next();
    },
    beforeRouteLeave (to, from, next) {
        console.log('beforeRouteLeave');
        console.log(to);
        console.log(from);
        next();
    },
    

    这样在进到路由相关页面这几个钩子函数就会执行,执行时机如函数字名的意思

    • beforeRouteEnter是进入到路由时执行
    • beforeRouteLeave是离开路由时执行
    • beforeRouteUpdate是路由更新时执行

    beforeRouteEnter, beforeRouteLeave(上一个路由的beforeRouteLeave是下一个路由的beforeRouteEnter)基本一看就知道如何执行,beforeRouteUpdate是在针对同一路由数据更新时触发

    // 在index.vue进行如下调整
    <router-link to="/pageA">Go to pageA</router-link>
    <router-link to="/pageA/900864">Go to pageA ID:900864</router-link>
    <router-link to="/pageA/9527">Go to pageA ID:9527</router-link>
    <router-link to="/pageB">Go to pageB</router-link>
    

    这样在点击两个pageA ID进行切换时就会发现,只会触发beforeRouteUpdate的钩子函数,官方还提供了watch方法来监听同一路由的变化,估计是之前没有beforeRouteUpdate才有的方法

    路由嵌套

    Vue本身可以进行多层组件嵌套,路由为了适配这种多层嵌套,路由本身也可以进行嵌套。

    // 在项目目录下新建childPageB-A.vue和childPageB-B.vue
    // childPageB-A.vue内容如下
    <template>
        <div>
            <p>{{content}}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    content: '这是页面B的子页面A'
                }
            }
        }
    </script>
    ...
    // childPageB-B.vue内容如下
    <template>
        <div>
            <p>{{content}}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    content: '这是页面B的子页面B'
                }
            }
        }
    </script>
    ...
    // index.js路由修改
    // 路由中children是用来设置嵌套的子路由,在子路由中path如果添加"/"会被当作根路由
    import pageA from './pageA.vue';
    import pageB from './pageB.vue';
    import pageBA from './childPageB-A.vue';
    import pageBB from './childPageB-B.vue';
    
    const routes =  [
        { path: '/pageA', component: pageA },
        { path: '/pageA/:id', component: pageA },
        { path: '/pageB', component: pageB, children:[
            { path: 'pageBA', component: pageBA},
            { path: 'pageBB', component: pageBB}
        ] }
    ];
    ...
    // index.vue做如下修改,添加到子路由的链接
    <router-link to="/pageB">Go to pageB</router-link>
    <router-link to="/pageB/pageBA">Go to pageBA</router-link>
    <router-link to="/pageB/pageBB">Go to pageBB</router-link>
    

    此时在页面端点击最后两个链接就会发现通过嵌套路由,pageB.vue的组件中也具备路由切换能力

    路由命名

    路由可以进行多层嵌套而且也可以接受参数,这就意味着在某些情况下路由会变的非常冗长,不如下面这种情况。

    // 项目目录下新建childPageA-A.vue,内容如下
    <template>
        <div>
            <p>{{content}} {{ $route.params.info }}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    content: '这是页面A的子页面A'
                }
            }
        }
    </script>
    ...
    // pageA.vue添加路由渲染区域
    <p>{{content}} {{ $route.params.id }}</p>
    <router-view></router-view>
    ...
    // index.js添加如下内容
    // 引入新建组件
    import pageAA from './childPageA-A.vue';
    ...
    // pageA路由添加children
     { path: '/pageA/:id', component: pageA, children: [
        {path: ':info', component: pageAA}
    ] },
    ...
    // index.vue添加如下内容
    <router-link to="/pageA/9527/info">Go to pageA ID:9527 info:info</router-link>
    

    上例中 to="/pageA/9527/info" 其实并不是一种很好的代码体验,这种代码逻辑给别人看的时候,或者一段时间以后自己会看代码时,根本就弄不清路由中相关参数的意义,针对这种情况,我们可以对路由进行命名,并且通过对象的形式传递相关参数

    // 在index.js下代码做如下调整
    // 在pageA的子路由添加name属性
    const routes =  [
        { path: '/pageA', component: pageA },
        { path: '/pageA/:id', component: pageA, children: [
            {path: ':info', name: 'pageA', component: pageAA}
        ] },
        { path: '/pageB', component: pageB, children:[
            { path: 'pageBA', component: pageBA},
            { path: 'pageBB', component: pageBB}
        ] }
    ];
    ...
    // index.vue下路由做调整
    // 注意这里的name属性设定在子路由中,是因为我们期望的URL是"/pageA/9527/info",params会按'value/value'的形式进行拼接,如果我们这时把name属性定义在page: 'pageA/:id'这一层,会因为没有形参接收info值,忽略info的传参,我们这时把name设定在子路由上,此时完整的路由如:'/pageA/:id/:info'
    <router-link :to="{ name: 'pageA', params: { id: '9527', info: 'info'} }">Go to pageA ID:9527 info:info</router-link>
    

    使用这种命名路由可以很清晰的看出我们传递的参数是什么

    命名视图

    通过上面的例子,我们可以看出,借助router-view我们可以实现,在不同区域展示不同的内容,如果我们在对视图进行命名,可以很容易实现类似iframe的页面组合的效果。

    // 项目目录下body.vue和footer.vue
    // header.vue内容修改如下
    <template>
        <div>
            <p>{{ title }}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    title: '这是头部'
                }
            }
        }
    </script>
    ...
    // body.vue内容如下
    <template>
        <div>
            <p>{{ content }}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    content: '这是body'
                }
            }
        }
    </script>
    ...
    // footer.vue内容如下
    <template>
        <div>
            <p>{{ footer }}</p>
        </div>
    </template>
    <script>
        export default {
            data () {
                return {
                    footer: '这是footer'
                }
            }
        }
    </script>
    ...
    // index.js引入新建组件
    import header from './header.vue';
    import body from './body.vue';
    import footer from './footer.vue';
    ...
    // index.js路由做相关调整
    const routes =  [
        { path: '/', components: {
            default: pageA,
            header: header,
            body: body,
            footer: footer
        }},
    ...
    // index.vue页面做相关调整
    <div class="content">
        <router-view></router-view>
        <router-view name="header"></router-view>
        <router-view name="body"></router-view>
        <router-view name="footer"></router-view>
    </div>
    

    这样在指定的视图中,就会渲染指定内容,如果router-view没有设置name就会默认为default

    编程式导航

    上面我们都是通过router-link的方式进行页面跳转,除了使用这种方式,我们还可以通过router的相关方法来进行跳转。

    router.push(location, onComplete?, onAbort?)

    这是插入一条历史记录的跳转方式,类似location.href,参数location是跳转地址,可以是字符形式,也可以是对象形式

    // index.vue添加如下代码
    <button @click="goPage">Go to pageA</button>
    ...
    // methods下添加goPage方法
    goPage () {
        // 效果和to="/pageA"一致
        // 字符形式
        this.$router.push('/pageA');
        // 对象形式
        // 渲染为/pageA/9527/info
        // 要留意使用path时,需不需要添加'/',这个会影响最终渲染的路由路径
        // this.$router.push({ name: 'pageA', params: { id: '9527', info: 'info'} });
        // 使用对象形式,如果使用了path,就会忽略params属性
        // 渲染结果为/pageA
        // this.$router.push({ path: '/pageA', params: { id: '9527', info: 'info'} });
        // 使用path时,可以使用query属性
        // 渲染为/pageA?id=9527&info=info
        // this.$router.push({ path: '/pageA', query: { id: '9527', info: 'info'} });
        // 对象形式使用name时,params和query都可以使用
        // 渲染为/pageA/9527/info?id=9527&info=info
        // this.$router.push({ name: 'pageA', params:{id: '9527', info: 'info'}, query: { id: '9527', info: 'info'} });
    }   
    

    至于onComplete和onAbort是在路由跳转完成和失败时分别执行的回调函数

    // 添加onComplete方法
    // 此时页面完成跳转后触发onComplete
    // onAbort应该是在路由被终止时触发,没想到怎么写这例子...
    goPage () {
        this.$router.push({ path: '/pageA' }, this.onComplete);
    },
    onComplete () {
        console.log('页面完成跳转')
    }
    

    router.replace(location, onComplete?, onAbort?)

    使用方式和push一致,区别在于replace不会产生新的浏览器历史记录,和location.replace类似

    router.go

    和history.go类似,接收一个数字表示进行历史记录前进几个后退几个

    重定向

    把某个路由指向到指定路由,使用redirect参数设定

    // 在index.js的路由中添加如下信息
    // 下面表示如果用户访问'/goPageA'会指向'/pageA'
    { path: '/goPageA', redirect: '/pageA' },
    ...
    // index.vue的模板中添加如下信息
    <router-link to="/goPageA">Go to goPageA</router-link>
    

    这时当我们点击页面中这个按钮时,页面会自动重定向到'/pageA',重定向还可以使用对象形式,并且可以传参数

    // index.js中路由信息修改
    // 下面信息重定向到/pageA/9527/info
    { path: '/goPageA', redirect: {name: 'pageA', params: {id: 9527, info: 'info'}} },
    

    别名

    把某些比较长的路由信息设定一个访问别名

    // 在index.js的路由进行如下修改
    // 对'/pageB/pageBB'这个路由设定一个'/BB'的别名
    { path: '/pageB', component: pageB, children:[
        { path: 'pageBA', component: pageBA},
        { path: 'pageBB', component: pageBB, alias: '/BB'}
    ] }
    ...
    // 在index.vue代码修改如下
    <router-link to="/BB">Go to BB</router-link>
    

    这时如果我们点击该按钮,页面会切换到pageBB的组件中,并且URL上路由为'/BB',当我们点击"Go to pageBB"链接,页面URL会变成'/pageB/pageBB',但页面内容不变,比较有意思的是这时vue-router会触发beforeRouteUpdate的钩子函数

    组件与路由的解耦

    回看我们之前设定的pageA组件

    // $route.params.id是从路由上取id参数来使用,这样很方便,但也限制了这个组件的复用 
    <p>{{content}} {{ $route.params.id }}</p>
    ...
    // 通过以下修改,我们把路由相关的参数提出props
    // pageA.vue页面修改如下
    <div>
        <p>{{content}} {{ id }}</p>
        <router-view></router-view>
    </div>
    ...
    props: ['id'],
    // childPageA-A.vue页面修改如下
    <p>{{content}} {{ info }}</p>
    ...
    props: ['info']
    ...
    // index.js中路由做如下修改
    // props设为true把$route.params转为了组件属性,
     { path: '/pageA/:id', component: pageA, props: true, children: [
        {path: ':info', name: 'pageA', props: true, component: pageAA}
    ] },
    // 如果把props设为一个对象,就会直接像组件传递这个对象中的数据,而忽略传递的参数
    // 比如下面这样写,则页面中所有的id都会被设为9876,这个特性在设定一些静态参数时比较好用
    { path: '/pageA/:id', component: pageA, props: { id: 9876 }, children: [
        {path: ':info', name: 'pageA', props: true, component: pageAA}
    ] },
    

    这时我们点击相关链接,发现路由传递的参数依然可以正常使用,而且通过上面的改造,上面的两个组件也与路由进行了解耦,可以当作普通组件使用

    导航的跳转监听

    vue-router提供了不少钩子函数来监听导航的跳转

    在实例化对象上的监听:beforeEach、beforeResolve、afterEach

    // 更改index.js路由配置信息
    // 添加在路由配置中调用的钩子函数beforeEnter
    { path: '/pageA/:id', component: pageA, props: true, beforeEnter: (to, from, next) => {
        console.log('beforeEnter');
        next();
    }, children: [
        {path: ':info', name: 'pageA', props: true, component: pageAA}
    ] },
    ...
    // 在index.js添加相关钩子函数
    const router = new VueRouter({
        routes // (简写)相当于 routes: routes
    });
    // beforeEach是要进入某个路由时最先执行的钩子函数
    router.beforeEach((to, from, next) => {
        console.log('beforeEach');
        console.log(to);
        console.log(from);
        next();
    });
    // beforeResolve是在组件路由的钩子函数执行完成后,执行这个钩子函数
    router.beforeResolve((to, from, next) => {
        console.log('beforeResolve');
        console.log(to);
        console.log(from);
        next();
    });
    // afterEach是在实例化路由和组件路由都执行完成后,最后执行的钩子函数,这个钩子函数不包含next
    router.afterEach((to, from) => {
        console.log('afterEach');
        console.log(to);
        console.log(from);
    });
    ...
    // 这时路由相关的钩子函数执行如下
    // 进入页面默认执行
    beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach
    ...
    // 随意切换路由
    beforeRouteLeave -> beforeEach -> beforeEnter -> beforeRouteEnter -> beforeResolve -> afterEach
    // 在同一级路由切换,这时路由模块并没有切换所以不会触发beforeRouteLeave
    beforeEach -> beforeRouteUpdate -> beforeResolve -> afterEach
    ...
    // 按官方文档的解析,整个路由在进行解析时,执行过程如下
    1. 导航被触发
    2. 如果路由进行切换时是准备进到一个新的路由,并且这个新的路由准备渲染的组件和之前路由的渲染组件不一致,就会尝试去执行旧组件中的beforeRouteLeave函数,如果是在同一个路由下,只是传参不同(比如"/pageA/9527"和"/pageA/900864"),这时并不会执行beforeRouteLeave而是直接执行第3步
    3. 尝试执行路由实例化对象beforeEach函数
    4-1. 如果路由只是参数不同,这时会尝试执行组件内beforeRouteUpdate函数
    4-2-1. 如果路由不同,这时会尝试执行路由配置项中的beforeEnter
    4-2-2. 解析异步路由组件(???没看明白指的是什么)
    4-2-3. 尝试调用被激活组件的beforeRouteEnter函数
    5. 尝试调用路由实例化对象的beforeResolve函数
    6. 导航解析完成
    7. 尝试调用路由实例化对象的afterEach
    8. 触发DOM更新
    9. 执行next回调
    
    

    next用来表示当前路由状态已经被解析,可以继续执行下面的逻辑,next默认不传参,如果传入不同参数,会对路由的执行产生不同的阻断或者跳转。

    next(false): 会阻断后续函数执行,并且停留在from路由
    next('/') 或 next('{ path: "/" }'): 阻断后续函数执行,并把路由指向要重定向的页面
    ...
    // 在index.js的路由中做如下修改
    // 这时你点击与pageA相关的导航,会自动定向到pageB,并且会把路由解析过程重修执行
    { path: '/pageA/:id', component: pageA, props: true, beforeEnter: (to, from, next) => {
        console.log('beforeEnter');
        next('/pageB');
    }, children: [
        {path: ':info', name: 'pageA', props: true, component: pageAA}
    ] },
    ...
    // 在pageA.vue下路由信息添加如下
    // next中接收的是一个错误的路由信息
    beforeRouteEnter (to, from, next) {
        console.log('beforeRouteEnter');
        console.log(to);
        console.log(from);
        next(this.printInfo);
    },
    ...
    // 如果你在路由对象实例化时注册了这个函数
    let errMsg = function () {
        console.log('路由解析发生错误,可以在这里进行异常处理');
    };
    
    router.onError(() => {
        console.log('onError');
        errMsg();
    });
    

    meta信息

    在定义路由时,可以设置meta信息来设置一些额外的信息。

    // 在index.js的定义路由中,设定相关meta信息
    { path: '/pageA', component: pageA, meta: { requiresAuth: true } },
    ...
    // 在pageA.vue中mounted获取定义的meta信息
    mounted () {
        console.log(this.$route.matched[0].meta);
    }
    

    过渡

    借用transition标签使router-view在切换内容时,能形成类似app切换的效果

    统一过渡效果

    // 在index.vue模板中做如下修改
    <div class="content">
        <transition name="fade" mode="out-in">
            <router-view></router-view>
        </transition>
    ...
    // 在index.vue的样式文件中添加如下样式
    <style>
        .fade-enter-active, .fade-leave-active {
            transition: all 1s;
            position: relative;
        }
        .fade-enter, .fade-leave-to {
            opacity: 0;
        }
        .fade-enter {
            left: 50px;
        }
        .fade-leave-to {
            left: -50px;
        }
        .fade-enter-to, .fade-leave {
            opacity: 1;
            left: 0;
        }
    </style>
    

    这样在切换不同组件时就会有app切入切出的效果

    不同路由组件的过渡效果

    // 清除index.vue下的模板过渡效果
    <div class="content">
        <router-view></router-view>
    ...
    // pageA.vue下,页面做下面调整
    <template>
        <transition name="silde">
            <div>
    ...
    // pageA.vue下添加下面样式
    <style>
        .silde-enter-active, .silde-leave-active {
            transition: all 1s;
            position: relative;
        }
        .silde-enter {
            left: 50px;
        }
        .silde-leave-to {
            left: -50px;
        }
        .silde-enter-to, .silde-leave {
            left: 0;
        }
    </style>
    ...
    // pageB.vue下添加过渡
    <template>
        <transition name="opacity">
            <div>
    ...
    // pageB.vue添加下面样式
    <style>
        .opacity-enter-active, .opacity-leave-active {
            transition: all 1s;
            position: relative;
        }
        .opacity-enter, .opacity-leave-to {
            opacity: 0;
        }
        .opacity-enter-to, .opacity-leave {
            opacity: 1;
        }
    </style>
    

    这样在切换pageA与pageB的相关路由页面时,会发现pageA与pageB会有不同的过渡效果

    滚动行为

    在切换页面时,可能会碰到滚动到指定位置的需求,vue-router对此进行了相关封装,使用scrollBehavior使页面滚动到指定位置

    // 在index.js中修改代码逻辑
    // 在scrollBehavior中return需要跳转的坐标,这样在路由切换时就会跳转到指定位置
    const router = new VueRouter({
        routes, // (简写)相当于 routes: routes
        scrollBehavior (to, from, savedPosition) {
            // return 期望滚动到哪个的位置
            return { x: 0, y: 200 }
        }
    });
    ...
    // 可以通过promise设置延时滚动
    scrollBehavior (to, from, savedPosition) {
        // return 期望滚动到哪个的位置
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve({ x: 0, y: 200 })
            }, 500)
        })
    }
    

    相关文章

      网友评论

          本文标题:Vue现代化使用方法(五)--vue-router

          本文链接:https://www.haomeiwen.com/subject/xpwzdftx.html