前几天写了 Vue状态管理模式:Vuex入门教程 ,今天再整理一下 Vue Router 的入门笔记。
什么是 Vue Router ?
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含如下功能:
嵌套的路由/视图表
模块化的、基于组件的路由配置
路由参数、查询、通配符
基于 Vue.js 过渡系统的视图过渡效果
细粒度的导航控制
带有自动激活的 CSS class 的链接
HTML5 历史模式或 hash 模式,在 IE9 中自动降级
自定义的滚动条行为
安装
npm install vue-router
在 main.js
中通过 Vue.use()
明确地安装路由功能:
import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter)
基础使用
router.js
or router/index.js
import Vue from 'vue' //1.导入 import Router from 'vue-router' import Home from './views/Home.vue' import About from './views/About.vue' //2.模块化机制 使用Router Vue.use(Router) //3.创建路由器对象 const router = new Router({ routes:[{ path: '/home', component: Home }, { path: '/about', component: About } ] }) export default router;
main.js
import Vue from 'vue' import App from './App.vue' import router from './router' Vue.config.productionTip = false new Vue({ router, // 4.挂载根实例 render: h => h(App) }).$mount('#app')
以上配置完后
App.vue
<template> <div id="app"> <div id="nav"> <!-- 使用 router-link 组件来导航 --> <!-- 通过传入 to 属性指定连接 --> <!-- router-link 默认会被渲染成一个 a 标签 --> <router-link to="/">Home</router-link> | <router-link to="/about">About</router-link> | </div> <!-- 路由出口 --> <!-- 路由匹配的组件将被渲染到这里 --> <router-view/> </div> </template>
运行项目,打开浏览器,切换 Home 和 About 超链接,查看效果。
命名路由
在配置路由的时候,给路由添加 name
属性,可以动态的根据 name
来进行访问。
const router = new Router({ routes:[{ path: '/home', name:"home", component: Home }, { path: '/about', name:'about' component: About } ] })
要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:
<router-link :to="{name:'home'}">Home</router-link> <router-link :to="{name:'about'}">About</router-link>
动态路由匹配
我们经常需要把某种模式匹配到的所有路由,映射到同个组件。例如,我们有一个 User 组件,对 ID 各不相同的用户,都使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果。
Us
er.vue
<template> <div> <h3>用户页面</h3> </div> </template> <script> export default { }; </script> <style scoped> </style>
路由配置:
const router = new Router({ routes:[ { path: '/user/:id', name: 'user', component: User, }, ] })
链接传参:
<router-link :to="{name:'user', params:{id:1}}">User</router-link>
访问链接:
http://localhost:8080/user/1
http://localhost:8080/user/2
查看效果。
当匹配到路由时,参数值会被设置到 this.$route.params
,可以在每个组件中使用,于是,我们可以更新 User 的模板,输出当前用户的 ID:
<template> <div> <h3>用户页面{{$route.params.id}}</h3> </div> </template>
响应路由参数的变化:
注意:当使用路由参数时,例如从 /user/1
导航到 /user/2
,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch
(监测变化) $route
对象:
/*使用watch(监测变化) $route对象 watch: { $route(to, from) { console.log(to.params.id); } }, */ // 或者使用导航守卫 beforeRouteUpdate(to,from,next){ //查看路由的变化 //一定要调用next,不然就会阻塞路由的变化 next(); }
404路由
const router = new Router({ routes:[ //.... // 匹配不到理由时,404页面显示 { path: '*', component: () => import('@/views/404') } ] })
当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' }
通常用于客户端 404 错误。
当使用一个通配符时,$route.params
内会自动添加一个名为 pathMatch
参数。它包含了 URL 通过通配符被匹配的部分:
{ path: '/user-*', component: () => import('@/views/User-admin.vue') } this.$route.params.pathMatch // 'admin'
匹配优先级:
有时候,同一个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
查询参数
像这种地址:http://localhos:8080/page?id=1&title=w3h5
const router = new Router({ routes:[ //.... { name:'/page', name:'page', component:()=>import('@/views/Page.vue') } ] })
<router-link :to="{name:'page',query:{id:1,title:'w3h5'}}">User</router-link>
访问 http://localhos:8080/page?id=1&title=w3h5
查看 Page
Page.vue
<template> <div> <h3>Page页面</h3> <h3>{{$route.query.userId}}</h3> </div> </template> <script> export default { created () { //查看路由信息对象 console.log(this.$route); }, } </script> <style scoped> </style>
路由重定向和别名
例如:从 /
重定向到 /home
:
const router = new Router({ mode: 'history', routes: [ // 重定向 { path: '/', redirect: '/home' }, { path: '/home', name: 'home', component: Home }, ] })
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({ routes: [ { path: '/', redirect: { name: 'name' }} ] })
甚至是一个方法:
const router = new VueRouter({ routes: [ { path: '/a', redirect: to => { // 方法接收 目标路由 作为参数 // return 重定向的 字符串路径/路径对象 }} ] })
别名:
{ path: '/home', name: 'home', component: Home, alias: '/alias' }
起别名,仅仅起起别名。用户访问 http://loacalhost:8080/alias
的时候,显示 Home 组件。
注意:别名的功能让你可以自由地将 UI 结构映射到任意的 URL,而不是受限于配置的嵌套路由结构。
路由组件传参
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
使用 props
将组件和路由解耦:
取代与 $route
的耦合
{ path: '/user/:id', name: 'user', component: User, props:true },
User.vue
<template> <div> <h3>用户页面{{$route.params.id}}</h3> <h3>用户页面{{id}}</h3> </div> </template> <script> export default{ //.... props: { id: { type: String, default: '' }, }, } </script>
props
也可以是函数:
{ path: '/user/:id', name: 'user', component: User, props: (route)=>({ id: route.params.id, title:route.query.title }) }
User.vue
<template> <div> <h3>用户页面{{id}}-{{title}}</h3> </div> </template> <script> export default { // ... props: { id: { type: String, default: '' }, title:{ type:String } }, }; </script>
编程式导航
除了使用 <router-link>
创建 a 标签来定义导航链接,我们还可以借助 router
的实例方法,通过编写代码来实现。
注意:在 Vue 实例内部,你可以通过 router
访问路由实例。因此你可以调用 this.router
访问路由实例。因此你可以调用 this.router.push
。
声明式:<router-link :to="...">
编程式:router.push(...)
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串 this.$router.push('home') // 对象 this.$router.push({ path: 'home' }) // 命名的路由 this.$router.push({ name: 'user', params: { userId: '123' }}) // 带查询参数,变成 /register?plan=private this.$.push({ path: 'register', query: { plan: 'private' }})
前进后退:
// 在浏览器记录中前进一步,等同于 history.forward() router.go(1) // 后退一步记录,等同于 history.back() router.go(-1) // 前进 3 步记录 router.go(3) // 如果 history 记录不够用,那就默默地失败呗 router.go(-100) router.go(100)
嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件。
/user/1/profile /user/1/posts +------------------+ +-----------------+ | User | | User | | +--------------+ | | +-------------+ | | | Profile | | +------------> | | Posts | | | | | | | | | | | +--------------+ | | +-------------+ | +------------------+ +-----------------+
router.js
{ path: '/user/:id', name: 'user', component: User, props: ({params,query})=>({ id: params.id, title:query.title }), children:[ // 当 /user/:id/profile 匹配成功, // Profile 会被渲染在 User 的 <router-view> 中 { path:"profile", component: Profile }, // 当 /user/:id/posts 匹配成功, // Posts 会被渲染在 User 的 <router-view> 中 { path: "posts", component: Posts } ] }
在 User
组件的模板添加一个 <router-view>
:
<template> <div> <h3>用户页面{{$route.params.id}}</h3> <h3>用户页面{{id}}</h3> <router-view></router-view> </div> </template>
App.vue
<template> <div id='app'> <!-- 嵌套理由 --> <router-link to="/user/1/profile">User/profile</router-link> | <router-link to="/user/1/posts">User/posts</router-link> | </div> </template>
命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。
{ path: '/home', name: 'home', //注意这个key是components components: { default: Home, //默认的名字 main: ()=>import('@/views/Main.vue'), sidebar: () => import('@/views/Sidebar.vue') } },
App.vue
<router-view/> <router-view name='main'/> <router-view name='sidebar'/>
导航守卫
“导航”表示路由正在发生改变。
完整的导航解析流程
导航被触发。
在失活的组件里调用离开守卫。
调用全局的
beforeEach
守卫。在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。在路由配置里调用
beforeEnter
。解析异步路由组件。
在被激活的组件里调用
beforeRouteEnter
。调用全局的
beforeResolve
守卫 (2.5+)。导航被确认。
调用全局的
afterEach
钩子。触发 DOM 更新。
用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
全局守卫:
你可以使用 router.beforeEach
注册一个全局前置守卫
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
有个需求,用户访问在浏览网站时,会访问很多组件,当用户跳转到 /notes
,发现用户没有登录,此时应该让用户登录才能查看,应该让用户跳转到登录页面,登录完成之后才可以查看我的笔记的内容,这个时候全局守卫起到了关键的作用。
有两个路由 /notes
和 /login
router.vue
const router = new VueRouter({ routes:[ { path: '/notes', name: 'notes', component: () => import('@/views/Notes') }, { path: "/login", name: "login", component: () => import('@/views/Login') }, ] }) // 全局守卫 router.beforeEach((to, from, next) => { //用户访问的是'/notes' if (to.path === '/notes') { //查看一下用户是否保存了登录状态信息 let user = JSON.parse(localStorage.getItem('user')) if (user) { //如果有,直接放行 next(); } else { //如果没有,用户跳转登录页面登录 next('/login') } } else { next(); } })
Login.vue
<template> <div> <input type="text" v-model="username"> <input type="password" v-model="pwd"> <button @click="handleLogin">提交</button> </div> </template> <script> export default { data() { return { username: "", pwd: "" }; }, methods: { handleLogin() { // 1.获取用户名和密码 // 2.与后端发生交互 setTimeout(() => { let data = { username: this.username }; //保存用户登录信息 localStorage.setItem("user", JSON.stringify(data)); // 跳转我的笔记页面 this.$router.push({ name: "notes" }); }, 1000); }, } }; </script>
App.vue
<!-- 全局守卫演示 --> <router-link to="/notes">我的笔记</router-link> | <router-link to="/login">登录</router-link> | <button @click="handleLogout">退出</button>
export default { methods: { handleLogout() { //删除登录状态信息 localStorage.removeItem("user"); //跳转到首页 this.$router.push('/') } }, }
组件内的守卫:
你可以在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
<template> <div> <h3>用户编辑页面</h3> <textarea name id cols="30" rows="10" v-model="content"></textarea> <button @click="saveData">保存</button> <div class="wrap" v-for="(item,index) in list" :key="index"> <p>{{item.title}}</p> </div> </div> </template> <script> export default { data() { return { content: "", list: [], confir: true }; }, methods: { saveData() { this.list.push({ title: this.content }); this.content = ""; } }, beforeRouteLeave(to, from, next) { // 导航离开该组件的对应路由时调用 // 可以访问组件实例 `this` if (this.content) { alert("请确保保存信息之后,再离开"); next(false); } else { next(); } } }; </script>
路由元信息实现权限控制:
给需要添加权限的路由设置 meta
字段
{ path: '/blog', name: 'blog', component: () => import('@/views/Blog'), meta: { requiresAuth: true } }, { // 路由独享的守卫 path: '/notes', name: 'notes', component: () => import('@/views/Notes'), meta: { requiresAuth: true } },
// 全局守卫 router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuth)) { // 需要权限 if(!localStorage.getItem('user')){ next({ path:'/login', query:{ redirect:to.fullPath } }) }else{ next(); } } else { next(); } })
login.vue
//登录操作 handleLogin() { // 1.获取用户名和密码 // 2.与后端发生交互 setTimeout(() => { let data = { username: this.username }; localStorage.setItem("user", JSON.stringify(data)); // 跳转到之前的页面 this.$router.push({path: this.$route.query.redirect }); }, 1000); }
数据获取:
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
导航完成后获取数据:
当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created
钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
<template> <div class="post"> <div v-if="loading" class="loading">Loading...</div> <div v-if="error" class="error">{{ error }}</div> <div v-if="post" class="content"> <h2>{{ post.title }}</h2> <p>{{ post.body }}</p> </div> </div> </template>
export default { name: "Post", data() { return { loading: false, post: null, error: null }; }, // 组件创建完后获取数据, // 此时 data 已经被 监视 了 created() { // 如果路由有变化,会再次执行该方法 this.fetchData(); }, watch: { $route: "fetchData" }, methods: { fetchData() { this.error = this.post = null; this.loading = true; this.$http.get('/api/post') .then((result) => { this.loading = false; this.post = result.data; }).catch((err) => { this.error = err.toString(); }); } } };
未经允许不得转载:前端资源网 - w3h5 » Vue官方路由管理器Vue-router入门教程