Vue路由总结

2019-03-14  本文已影响0人  李牧敲代码

Vue Router 官网已经写得很好了,这里自己再总结巩固下。(注意:这里所有的例子都是基于Vue/cli 3.4的脚手架进行讲解的)
Vue的路由分两种:编程式路由声明式路由

vm.$route.push('/路由地址')  //编程式路由
<router-link to="/路由地址"></router-link>   //声明式路由

Vue Router可以分以下几个模块来讲解

动态路由匹配

【应用场景】

假设我们有个模板是展示用户信息的,在上一个模板中,点击不同的用户ID进入相同的模板,但是展示的用户信息是不一样的。就像这样:
/user/user1/user/user2,当然还有其他方式,我们先用这种方式。看代码:

<!-- 先写一个视图用于放置声明式路由 -->
<template>
    <div>
        home
        <router-link to="/componentA/user1">user1</router-link>
        <router-link to="/componentA/user2">user2</router-link>
    </div>
</template>

<script>
    // @ is an alias to /src
    export default {

    }
</script>

//在脚手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import componentA from './components/componentA.vue'

Vue.use(Router)

export default new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            path: '/',
            name: 'home',
            component: Home
        },
        {
            path: '/componentA/:id/', //这里就是动态路由,通过id来进行动态匹配
            component: componentA
        }
    ]
})
<!-- componentA -->
<template>
    <div>
        componentA: {{$route.params.id}}
        <button @click="goBack">goBack</button>
    </div>
</template>
<script>
    export default {
        name: 'componentA',
        methods: {
            goBack() {
                this.$router.push('/')
            },
        }
    }
</script>
<style lang="">

</style>
image
【响应路由参数变化注意点】
  1. 组件会被复用
  2. 组件生命周期钩子不会被调用
  3. 可以通过 watch方法或者 beforeRouteUpdate监听动态路由变化
    watch() {
        '$route'(to, from) {
            //...
        }
    },
    beforeRouteUpdate(to, from, next) {
        console.log("to", to);
        console.log("from", from);
        console.log("next", next);
    },

嵌套路由

【应用场景】

比如做个tab页,就像下图这样:


test.gif

这个时候就要用到嵌套视图了,看代码:
现在App.vue里放个路由视图(也就是<router-view></router-view这个组件)

<template>
<!-- App.vue -->
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<style lang="less">
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
#nav {
  padding: 30px;
  a {
    font-weight: bold;
    color: #2c3e50;
    &.router-link-exact-active {
      color: #42b983;
    }
  }
}
</style>

先写一个组件充当父路由的组件

<template>
<!-- tab.vue -->
    <div class="tab">
        <router-link to="/index/a">首页</router-link>
        <router-link to="/index/b">技术页</router-link>
        <!-- 这里很重要,如果父路由里的组件里没有相应的路由视图,那么子路由导航后是无法展现的 -->
        <router-view></router-view>
    </div>
</template>

<script>
import Vue from 'vue';
// @ is an alias to /src
export default {
    name: 'tab',
};
</script>
<style scoped>
    .tab {
        background-color: yellow;
    }
</style>


然后随便写2个组件视图

<template>
<!-- componentA_a.vue -->
    <div class="index">
        <div class="index">
            首页
        </div>
        
    </div>
</template>
<script>
export default {
    name: "componentA_a",
};
</script>
<style scoped>
    .index {
        background-color: red;
    }
</style>
<template>
<!-- componentA_b.vue -->
    <div class="tec">
        <div class="tec">技术页</div>
    </div>
</template>
<script>
export default {
    name: "componentA_b",
    created() {
        console.log(123);
    }
};
</script>
<style scoped>
.tec {
    background-color: lightblue;
}
</style>

最后配置router.js

import Vue from 'vue'
import Router from 'vue-router'
import tab from './views/tab.vue'

Vue.use(Router)

export default new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            path: '/',
            redirect: '/index' //重定向到/index路由
        },
        {
            path: '/index',
            component: tab,
            children: [
                {
                    path: '',
                    redirect: 'a' //重定向路由,当路径是空的时候可以重定向路由也可以提供一个组件
                },
                {
                    path: 'a',
                    name: "componentA_a",
                    component: () => import('./components/componentA_a.vue')                    
                },
                {
                    path: 'b',
                    name: "componentA_b",
                    component: () => import('./components/componentA_b.vue')                    
                }
            ]
        }
    ]
})

这样就实现了嵌套路由,效果就是前面那张图。
PS:

编程式导航

【应用场景】

大多数情况还是编程式导航的吧,毕竟编程式导航用起来更加灵活。我可以对任务组件绑定事件,触发路由导航,而且可以随性所欲的传参。
3个方法

想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。
PS: <router-link :to="..."> 等同于调用 router.push(...)。
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:

// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

注意:如果提供了 path,params 会被忽略,上述例子中的 query 并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name 或手写完整的带有参数的 path:

const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user

同样的规则也适用于 router-link 组件的 to 属性。
在 2.2.0+,可选的在 router.push 或 router.replace 中提供 onComplete 和 onAbort 回调作为第二个和第三个参数。这些回调将会在导航成功完成 (在所有的异步钩子被解析之后) 或终止 (导航到相同的路由、或在当前导航完成之前导航到另一个不同的路由) 的时候进行相应的调用。
注意: 如果目的地和当前路由相同,只有参数发生了改变 (比如从一个用户资料到另一个 /users/1 -> /users/2),你需要使用 beforeRouteUpdate 来响应这个变化 (比如抓取用户信息)。

这个方法的参数是一个整数,意思是在 history 记录中向前或者后退多少步,类似 window.history.go(n)

// 在浏览器记录中前进一步,等同于 history.forward()
router.go(1)

// 后退一步记录,等同于 history.back()
router.go(-1)

// 前进 3 步记录
router.go(3)

// 如果 history 记录不够用,那就默默地失败呗
router.go(-100)
router.go(100)

命名路由

在前面的例子中你可能已经注意到了,比如这样

router.push({ name: 'user', params: { userId: '123' }})

或者

<router-link :to="{ name: 'user', params: { userId: 123 }}"></router-link>

这2种方法是等效的,这样就省去了path参数,

命名视图

【应用场景】

比如你一个路由里的组件需要多个子组件布局,这几个子组件是公共复用的,那么通过命名路由视图的方式将大大降低你组件的耦合度和发杂性。
看代码:

<template>
    <!-- App.vue -->
    <div id="app">
        <router-view name="header"></router-view>
        <router-view name="body"></router-view>
    </div>
</template>

<style lang="less">
    #app {
        font-family: 'Avenir', Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
    }

    #nav {
        padding: 30px;

        a {
            font-weight: bold;
            color: #2c3e50;

            &.router-link-exact-active {
                color: #42b983;
            }
        }
    }
</style>

//在脚手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import componentA from './components/componentA.vue'
import componentB from './components/componentB.vue'

Vue.use(Router)

export default new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            path: '/',
            name: 'home',
            components: { //加个s
                body: componentB,
                header: componentA,
            }
        }
    ]
})

把刚才视图组件补齐

<template>
    <!-- componentA.vue -->
    <div>
        <div class="header">
            header
        </div>

    </div>
</template>
<script>
    export default {
        name: 'componentA',
    }
</script>
<style lang="less" scoped>
    .header {
        background-color: lightgreen;
    }
</style>
<template>
    <!-- componentB.vue -->
    <div>
        <div class="bodyer">
            body
        </div>
    </div>
</template>
<script>
    export default {
        name: 'componentB',
    }
</script>
<style scoped lang="less">
    .bodyer {
        background-color: lightgrey;
    }
</style>

效果:


image

重定向和别名

【应用场景】

重定向的应用场景还是比较多的吧,经常用的就有默认路由重定向,别名就是给配置的路由名换个马甲,隐藏下真实的路由名吧,其他用处还没想到。
重定向例子:
重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

重定向的目标也可以是一个命名的路由:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: { name: 'foo' }}
  ]
})

甚至是一个方法,动态返回重定向目标:

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: to => {
      // 方法接收 目标路由 作为参数
      // return 重定向的 字符串路径/路径对象
    }}
  ]
})

别名例子:
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

绝对别名
const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})
默认别名
const router = new VueRouter({
  routes: [
  { path: 'default', component: Default, alias: '' },
  ]
})
多个别名
const router = new VueRouter({
  routes: [
  { path: 'baz', component: Baz, alias: ['/baz', 'baz-alias'] },
  ]
})

路由组件传参

先看代码

<template>
    <div>
        &nbsp;
        <router-link to="/b/c">c</router-link>// 这里我传了个参数“c”
    </div>
</template>

<script>
    // @ is an alias to /src
    export default {
    }
</script>

//在脚手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import componentA from './components/componentA.vue'
import componentB from './components/componentB.vue'

Vue.use(Router)

const router = new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            path: '/',
            name: 'home',
            component: Home
        },       
        {
            path: '/b/:id',
            name: 'b',
            component: componentB,
            props: (route) => ({  //这里也可以通过props: true, 或者 props: {id : true}来传参 
                id: route.params.id
            })
        }
    ]
});
export default router;
<template>
    <!-- componentB.vue -->
    <div>
        <div class="bodyer">
            body: {{id}}
        </div>
    </div>
</template>
<script>
    export default {
        props: ['id'], //通过props接受参数
        name: 'componentB',
    }
</script>
<style scoped lang="less">
    .bodyer {
        background-color: lightgrey;
    }
</style>

HTML5 History 模式

Vue-router是基于H5的history模式来实现的

导航守卫

记住参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate 的组件内守卫。

全局前置守卫
const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

全局解析守卫

router.beforeResolve和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

全局后置钩子

你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 next 函数也不会改变导航本身:

router.afterEach((to, from) => {
  // ...
})

路由独享的守卫(beforeEnter)

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

组件内的守卫

const Foo = {
  template: `...`,
  beforeRouteEnter (to, from, next) {
  next(vm => {
  console.log(vm)//vm就是组件实例,也就是this
  })
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不!能!获取组件实例 `this`
    // 因为当守卫执行前,组件实例还没被创建
  },
  beforeRouteUpdate (to, from, next) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 `this`
  },
  beforeRouteLeave (to, from, next) {
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 `this`
  }
}

完整的解析流程

完整的导航解析流程
导航被触发。
在失活的组件里调用离开守卫。beforeRouteleave
调用全局的 beforeEach 守卫。
在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
在路由配置里调用 beforeEnter。
解析异步路由组件。
在被激活的组件里调用 beforeRouteEnter。
调用全局的 beforeResolve 守卫 (2.5+)。
导航被确认。
调用全局的 afterEach 钩子。
触发 DOM 更新。
用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

路由元信息

这个东西特别适合权限检查,比如说登录。在路由配置里给每个路由做个标记,需要检查权限的标记下,那么不同权限就可以按照权限标准浏览页面了,岂不是美滋滋。
看代码:

<template>
    <!-- Home.vue -->
    <div>
        欢迎进入首页
    </div>
</template>

<script>
    // @ is an alias to /src
    export default {

    }
</script>

//在脚手架里的router.js里配置路由
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
import login from './views/login.vue'
import componentA from './components/componentA.vue'
import componentB from './components/componentB.vue'

Vue.use(Router)

const router = new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            path: '/',
            name: 'home',
            component: Home,
            meta: {
                isRequireLogin: true
            },
            beforeEnter(to, from, next) { //具体判断逻辑不写了
                let judeLogin = () => {
                    let a = Math.random();
                    console.log(a)
                    if(a > 0.5) {
                        return false
                    }else {
                        return true
                    }
                }
                console.log(to.matched)
                if(to.matched.some(record => record.meta.isRequireLogin)) {
                    if(judeLogin()) {
                        next()
                    } else {
                        next('/login')
                    }
                }else {
                    next('/login')
                }                
            }
        },       
        {
            path: '/login',
            name: 'login',
            component: login
        }   
    ]
});
export default router;
<template>
    <!-- login.vue -->
    <div>
        登录页面
    </div>
</template>

<script>
    // @ is an alias to /src
    export default {
        methods: {

        }
    }
</script>

看效果:这里姑且当随机数大于0.5的时候,显示登录失败


image

数据获取

有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:

个人觉得如果后端数据快的话用后面一种,快的话用前面那种。
第一种不用说吧,直接在生命钩子create()里来实现,后面那种看代码:

  beforeRouteEnter (to, from, next) {
    //getPost是自定义的请求接口
    getPost(to.params.id, (err, post) => {
      next(vm => vm.setData(err, post))
    })
  },

只要在beforeRouteEnter 这个路由守卫里实现就好了

路由懒加载

当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
庆幸的是Vue/cli脚手架默认就是懒加载(webpack的方式),其他方式我暂时用不到,有兴趣的话可以看官网。

【例子】
import('./Foo.vue') // 返回 Promise

【完】

上一篇下一篇

猜你喜欢

热点阅读