前几天写了 Vue状态管理模式:Vuex入门教程 ,今天再整理一下 Vue Router 的入门笔记。
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含如下功能:
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) 来达到这个效果。
User.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();
}
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
钩子。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-Web前端开发资源网 » Vue官方路由管理器Vue-router入门教程