安装:
npm install vue-router
// router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
// 模块化工程中,Vue.use() 是必须的
Vue.use(VueRouter);
// 抛出模块,用于跟组件注册
const router = new VueRouter({
// ...
});
export default router
// main.js 需要引入 router
import router from './router/index.js';
// vue根组件注册 router
new Vue({
router: router
});
单页应用:
<!-- 单文件项目,可使用全局的script标签,省略前三步 -->
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
最简单的例子:
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
<script>
// 1. 定义 (路由) 组件。(可以从其他文件 import 进来)
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
// 每个路由应该映射一个组件。 其中"component" 可以是通过 Vue.extend() 创建的组件构造器,或者,只是一个组件配置对象。
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. 创建 router 实例,然后传 `routes` 配置 (还有其他配置参数)
const router = new VueRouter({
routes: routes
})
// 4. 创建和挂载根实例。让整个应用都有路由功能
const app = new Vue({
router
// ...
})
</script>
组件可以通过 "this.$router" 访问路由器,通过 "this.$route" 访问当前路由。 官方完整例子
routes:
1. 动态路由匹配: [ "path" ]
// 有如下 routes 配置
routes: [
// 动态路径参数 以冒号开头
{ path: '/user/:id', component: User }
]
// 像 /user/foo 和 /user/bar 都将映射到相同的路由。
// User 组件内可以访问路径参数 "id"
console.log(this.$route.params); // { id: 'foo' }
// 像 /user/:username/post/:post_id 的多段"路径参数"
console.log(this.$route.params); // { username: 'evan', post_id: '123' }
注意,当使用路由参数时,如从 /user/foo 导航到 /user/bar ,组件实例会被复用,其生命周期函数将不会被调用。
2. 嵌套路由: [ "children" ]
// 组件的模板需要添加一个 <router-view>
const User = {
template: `
<div class="user">
<h2>User {{ $route.params.id }}</h2>
<router-view></router-view>
</div>
`
}
const router = new VueRouter({
routes: [{
path: '/user/:id', component: User,
// children 配置与 route 类似
children: [
// 当 /user/:id/profile 匹配成功,UserProfile 会被渲染在 User 的 <router-view> 中
{ path: 'profile', component: UserProfile },
// 当 /user/:id/posts 匹配成功,UserPosts 会被渲染在 User 的 <router-view> 中
{ path: 'posts', component: UserPosts },
// 当你访问 /user/foo 时,User 的出口是不会渲染任何东西,需要提供一个"空的"子路由
{ path: '', component: UserHome }
]
}]
})
注意,以 "/" 开头的嵌套路径会被当作根路径,合理做法是使用"空路径"(" ")。 官方完整例子
3. 单视图组件注册: [ "component" ]
<div>
<router-view></router-view>
</div>
<script>
const Foo = { template: `<div>Foo</div>` }
const router = new VueRouter({
routes: [{
path: '/',
// 通过本文件定义
component: Foo
}, {
path: '/user',
// 通过 异步懒加载 外部文件
component: () => import('./User.vue')
}]
})
</script>
4. 多视图组件注册: [ "components" <router-view name="a"> ] 官方完整例子 嵌套命名视图例子
<div>
<!-- 同一个路由地址下显示多个"路由视图" -->
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
</div>
<script>
// 假设已经引入或定义组件 Foo/Bar/Baz
const router = new VueRouter({
routes: [{
path: '/',
// 有别于"component"只能注册单个组件,"components"可以注册多个组件
components: {
// 匿名使用"default"注册
default: Foo,
a: Bar,
b: Baz
}
}]
})
</script>
5. 命名路由: [ "name" ]
const router = new VueRouter({
routes: [{
path: '/user/:userId',
// 用于 路由跳转
name: 'user',
component: User
}]
})
// 使用"声明式"路由跳转
// <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
// 使用"函数式"路由跳转
// router.push({ name: 'user', params: { userId: 123 }})
6. 路由 跳转/替换: [ "<router-link>" | "router.push()" ]
方法1,声明式,使用 <router-link :to="..."> 创建 a 标签来定义导航链接。
<!-- :to="..." 的规则与 函数式 的一致,等效 router.push({name:'user',params:{userId:123}}) -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<!-- 添加"replace"后,本路径会替换当前"history"记录,等效 router.replace({path:'/user/123'}) -->
<router-link :to="/user/123" replace>
<!-- 当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active。 -->
方法2,编程式,使用 router.push(...)
// 若路径已用"name"命名,可以用 name 代替 path
router.push({ path: 'home' })
// "/user?plan=private"
router.push({ name: 'user', query: { plan: 'private' }})
// 注意:如果提供了 path,params 会被忽略,正确写法如下:(已定义 userId=123 )
router.push({ path: `/user/${userId}` }) // -> /user/123
// 替换当前路由(覆盖当前的history记录),用法与 .push() 一致
router.replace(...)
// 前进/后退,n 是整数,负数表示后退
router.go(n)
7. 重定向 & 别名 : [ "redirect" & "alias" ]
const router = new VueRouter({
routes: [
// 重定向
// 当用户访问 /a时,URL 将会被替换成 /b
{ path: '/a', redirect: '/b' },
// 重定向到指定 name
{ path: '/a', redirect: { name: 'foo' }},
// 别名
// 当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a
{ path: '/a', component: A, alias: '/b' }
]
})
8. 路由组件传参: [ "props" ] 个人完整在线demo
<div id="app">
<router-link to="/vue_a/123">/vue_a/123</router-link><br>
<router-link to="/vue_b">/vue_b</router-link><br>
<router-link to="/vue_c?q=query">/vue_c?q=query</router-link><hr>
<router-view></router-view>
</div>
<script>
const vue_a = {
template: `<div> vue_a parmas.id : {{id}} </div>`,
props: ['id']
}
const vue_b = {
template: `<div> vue_b 静态props: {{vue_b_prop}} </div>`,
props: ['vue_b_prop']
}
const vue_c = {
template: `<div> vue_c query传参: {{query}} </div>`,
props: ['query']
}
const router = new VueRouter({
routes: [
// 布尔模式。props 被设置为 true,route.params 将会被设置为组件属性。
{ path: '/vue_a/:id', component: vue_a, props: true },
// 对象模式。传递静态参数
{ path: '/vue_b', component: vue_b, props: {vue_b_prop: 'aaaa'} },
// 函数模式。URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。
{ path: '/vue_c', component: vue_c, props: (route) => ({query: route.query.q}) }
]
});
new Vue({
el: "#app",
router
});
</script>
9. History模式: [ "mode" ]
const router = new VueRouter({
// 默认是"hash"
mode: 'history',
routes: [...]
})
10. 路由元信息: [ "meta" ]
// 官方的例子是利用"meta"的"requiresAuth"属性,实现组件是否需要权限访问
routes: [{
path: '/foo',
component: Foo,
children: [{
path: 'bar',
component: Bar,
// "requiresAuth"为自定义字段
meta: { requiresAuth: true }
}]
}]
// 在全局导航守卫中检查元字段:
router.beforeEach((to, from, next) => {
// 获取目标路径是否带有指定的"meta"字段,并进行相关鉴权或操作
if (to.matched.some(record => record.meta.requiresAuth)) {
// 鉴定是否登陆,否则跳转到登陆页面
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else { next() }
} else {
next() // 确保一定要调用 next()
}
})
滚动行为: [ "scrollBehavior(to, from, savedPosition)" ]
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置,格式: { x: number, y: number }
// scrollBehavior 的值就是{x:number,y:number}格式的对象
}
})
导航守卫:
1. 全局守卫:
const router = new VueRouter({ ... })
// 全局前置守卫
router.beforeEach((to, from, next) => { ... })
// 全局后置钩子,不接受next()
router.afterEach((to, from) => { ... })
2. 路由独享守卫:
const router = new VueRouter({
routes: [{
path: '/foo',
component: Foo,
// 路由独享守卫
beforeEnter: (to, from, next) => { ... }
}]
})
3. 组件内的守卫:
const Foo = {
template: `...`,
// 不能访问'this',但可以通过next()回调参数访问当前组件,如下
beforeRouteEnter (to, from, next) {
next(vm => { ... })
},
// 可以访问'this',只有在动态路径之间跳转才触发
beforeRouteUpdate (to, from, next) { ... },
// 可以访问'this',只有在非动态路径之间跳转才触发,该函数常用于判断用户是否保存,否则阻止跳转,如下
beforeRouteLeave (to, from, next) {
if (confirm) { next(); } else { next(false) }
}
}
注意, "next()" 钩子函数必须调用。
完整的导航解析流程:
遇到的问题总结:
1. 父级路由不能含有 "name" 属性,否则会提示如下错误:
2. 关于路由的两种模式: hash模式 和 history 模式
视觉上的区别是:hash模式下,主机名后面带有"#",更多查看 其他博客 。