Vue全家桶__Vue-router&Vuex
前介
多页应用 MPA 每一个页面都是一个.html文件 SEO优化
单页应用 SPA 相当于一个a标签,切换不同的视图
是否用户群体比较多
知乎 掘金 网易云 百度移动端
后台管理系统 vue-element-admin
Vue-Router
资料
介绍
Vue Router是vue.js官方的路由管理器它和Vue.js的 核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
- 嵌套的路由/视图表
- 模块化的,基于组件的路由配置
- 路由参数、查询、通配符
- 基于Vue.js过渡系统的视图过渡效果
- 细粒度的导航控制
- 带有自动激活的CSS class的链接
- HTML5历史模式或hash模式。在IE9中自动降级
- 自定义的滚动条行为
起步
用Vue.js+Vue Router创建单页应用,是非常简单的。使用Vue.js,我们已经可以通过组件来组成应用程序,当你要把Vue Router添加进来,我们需要做的是将组件(components)映射到路由(routes),然后告诉Vue Router 在哪里渲染它们
安装
npm i vue-router -S
创建项目
在自己想要创建项目的目录路径下执行vue create myproject
进行配置项的选择,空格表示选中安装
生成后的目录结构
基本使用
① 在src/router/index.js
// 0、引入Vue
import Vue from 'vue';
// 1、引入vue-router
import VueRouter from 'vue-router';
// 2、模块化机制,使用Router
Vue.use(VueRouter)
import Home from '../views/Home.vue';
import About from '../views/About.vue';
// 3、创建路由器对象
const router = new VueRouter({
routes:[ //rouutes是一个数组
{
path: "/",
component:Home
},
{
path: '/about',
component:About,
}
]
})
// 4、抛出路由对象
export default router;
② 在 src/main.js中
import Vue from 'vue'
import App from './App.vue'
// 导入router对象
import router from './router'
Vue.config.productionTip = false
new Vue({
// 5、挂载到vue实例中
router,
render: h => h(App)
}).$mount('#app')
③ 在 src/App.vue中
<template>
<div id="app">
<!--6、router-link访问对应的路由,通过router-view来渲染路由组件-->
<!--router-link 相当于a标签 to属性相当于a标签的href-->
<!--基本配置-->
<router-link to='/'>首页</router-link>
<router-link to='/about'>关于</router-link>
<!-- router-view 相当于路由组件的出口-->
<router-view></router-view>
</div>
</template>
<style>
#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;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
命名路由
在配置路由的时候,给路由添加名字,访问时就可以动态的根据名字来进
⾏访问
src/router/index.js
import Vue from 'vue';
// 1、导入
import VueRouter from 'vue-router';
// 2、模块化机制,使用Router
Vue.use(VueRouter)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';
// 3、创建路由器对象
const router = new VueRouter({
routes:[
{
path: "/",
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: User
}
]
})
// 4、抛出路由对象
export default router;
src/App.vue
<template>
<div id="app">
<!--6、router-link访问对应的路由,通过router-view来渲染路由组件-->
<!--router-link 相当于a标签 to属性相当于a标签的href-->
<!--命名路由配置-->
<router-link :to="{name:'home'}">首页</router-link>|
<router-link :to="{name:'about'}">关于</router-link>|
<router-link :to="{name:'user',params:{id:1}}">用户1</router-link>|
<router-link :to="{name:'user',params:{id:2}}">用户2</router-link>
<!-- router-view 相当于路由组件的出口-->
<router-view></router-view>
</div>
</template>
<style>
#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;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有⼀个 User 组件,对于所有 ID 各不相同的⽤户,都要使⽤这个组件来渲染。那么,我们可以在 vue-router 的路由路径中
使⽤“动态路径参数”(dynamic segment) 来达到这个效果
src/view/User.Vue
<template>
<div>
用户页面 {$route.params.id}
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
src/router/index.js
import Vue from 'vue';
// 1、导入
import VueRouter from 'vue-router';
// 2、模块化机制,使用Router
Vue.use(VueRouter)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';
// 3、创建路由器对象
const router = new VueRouter({
routes:[
{
path: "/",
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: User
}
]
})
// 4、抛出路由对象
export default router;
src/App.vue
<template>
<div id="app">
<!--6、router-link访问对应的路由,通过router-view来渲染路由组件-->
<!--router-link 相当于a标签 to属性相当于a标签的href-->
<!--命名路由配置-->
<router-link :to="{name:'home'}">首页</router-link>|
<router-link :to="{name:'about'}">关于</router-link>|
<router-link :to="{name:'user',params:{id:1}}">用户1</router-link>|
<router-link :to="{name:'user',params:{id:2}}">用户2</router-link>
<!--命名路由-->
<!-- <router-link :to="{name:'home'}">首页</router-link>|
<router-link :to="{name:'about'}">关于</router-link> -->
<!-- router-view 相当于路由组件的出口-->
<router-view></router-view>
</div>
</template>
<style>
#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;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
访问
查看效果
当匹配到路由时,参数值会被设置到this.$route.params,可以在每个组件中使⽤,于是,我们可以更新 User 的模板,输出当前⽤户的 ID:
src/views/User.vue
<template>
<div>
<h3>用户页面 {$route.params.id}</h3>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
在router/index.js的配置文件中,和routes同级的路径下,写出一行这个mode: history,
那么在浏览器访问的时候就会是干净的网页地址。
响应路由参数的变化
当使⽤路由参数时,例如从 /user/1 导航到 /user/2`, 原来的组件实例会被复⽤。因为两个路由都渲染同个组件,⽐起销毁再创建,复⽤则显得更加⾼效。 不过,这也意味着组件的⽣命周期钩⼦不会再被调⽤。
复⽤组件时,想对路由参数的变化作出响应的话,你可以简单地watch (监测变化) $route 对象:
src/views/User.vue
<template>
<div>
用户页面 {$route.params.id}
</div>
</template>
<script>
export default {
// 当路由的参数变化时 /user/1 切换到/user/2 原来的组件实例会被复用
// 因为两个路由渲染了同个组件,复用高效
created(){
console.log(this.$route.params.id);
},
watch:{
$route:(to)=>{
console.log(to.params.id);
//发起ajax 请求后端接口数据,数据趋势视图
}
}
// beforeRouteUpdate(to,from,next){
// console.log(to.params.id);
// // 查看路由的变化
// // 一定要调用next(),不然会阻塞整个路由 放行
// next();
// }
}
</script>
<style lang="scss" scoped>
</style>
404路由
src/router/index.js
import Vue from 'vue';
// 1、导入
import VueRouter from 'vue-router';
// 2、模块化机制,使用Router
Vue.use(VueRouter)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';
// 3、创建路由器对象
const router = new VueRouter({
routes:[
{
path: "/",
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: User
},
{
path: '*',
// 局部导入,异步加载组件
component:()=>import('@/views/404')
},
]
})
// 4、抛出路由对象
export default router;
在src/views/下新建404.vue
src/views/404.vue
<template>
<div>
<h3>404页面</h3>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
效果:
当使⽤通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常⽤于客户端 404错误
当使⽤⼀个通配符时, $route.params 内会⾃动添加⼀个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:
src/router/index.js
import Vue from 'vue';
// 1、导入
import VueRouter from 'vue-router';
// 2、模块化机制,使用Router
Vue.use(VueRouter)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';
// 3、创建路由器对象
const router = new VueRouter({
routes:[
{
path: "/",
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: User
},
{
path: '/user-*',
component: () => import('@/views/User-admin')
},
{
path: '*',
// 局部导入,异步加载组件
component: () => import('@/views/404')
},
]
})
// 4、抛出路由对象
export default router;
src/views/User-admin.vue
<template>
<div>
<h3>User-admin页面</h3>
<h4>{{$route.params.pathMatch}}</h4>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
匹配优先级
有时候,同⼀个路径可以匹配多个路由,此时,匹配的优先级就按照路由的定义顺序:谁先定义的,谁的优先级就最高。
查询参数
src/router/index.js
import Vue from 'vue';
// 1、导入
import VueRouter from 'vue-router';
// 2、模块化机制,使用Router
Vue.use(VueRouter)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';
// 3、创建路由器对象
const router = new VueRouter({
mode:'history', //历史模式,干净的网页地址
routes:[
{
path: "/",
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
component: About
},
{
path: '/user/:id',
name: 'user',
component: User
},
{
path: '/page',
name: 'page',
component: ()=> import('@/views/Page')
},
]
})
// 4、抛出路由对象
export default router;
src/App.vue
<template>
<div id="app">
<!--6、router-link访问对应的路由,通过router-view来渲染路由组件-->
<!--router-link 相当于a标签 to属性相当于a标签的href-->
<!--命名路由配置-->
<router-link :to="{name:'home'}">首页</router-link> |
<router-link :to="{name:'about'}">关于</router-link> |
<router-link :to="{name:'user',params:{id:1}}">用户1</router-link> |
<router-link :to="{name:'user',params:{id:2}}">用户2</router-link> |
<router-link :to="{name:'page',query:{id:1,title:'foo'}}">page</router-link>
<!--命名路由-->
<!-- <router-link :to="{name:'home'}">首页</router-link>|
<router-link :to="{name:'about'}">关于</router-link> -->
<!-- router-view 相当于路由组件的出口-->
<router-view></router-view>
</div>
</template>
<style>
#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;
}
#nav a {
font-weight: bold;
color: #2c3e50;
}
#nav a.router-link-exact-active {
color: #42b983;
}
</style>
src/views/page.vue
<template>
<div>
<h3>Page页面</h3>
</div>
</template>
<script>
export default {
created () {
// console.log(this.$route);
// 获取参数
const {id,title} = this.$route.query;
console.log(id,title);
// 与后端发生交互
},
}
</script>
<style lang="scss" scoped>
</style>
路由重定向和别名
路由重定向:例⼦是从 / 重定向到 /home :
路由起别名:例子 是访问/aaa显示初page的页面
别名”的功能让你可以⾃由地将 UI 结构映射到任意的 URL,⽽不是受限于配置的嵌套路由结构。
import Vue from 'vue';
// 1、导入
import VueRouter from 'vue-router';
// 2、模块化机制,使用Router
Vue.use(VueRouter)
import Home from '@/views/Home';
import About from '@/views/About';
import User from '@/views/User';
// 3、创建路由器对象
const router = new VueRouter({
mode:'history', //历史模式,干净的网页地址
routes:[
{
path: '/',
// --路由重定向---
// redirect: '/home',
redirect: {name:"home"},
},
{
path: "/home",
name: 'home',
component: Home
},
{
path: '/page',
name: 'page',
component: ()=> import('@/views/Page'),
alias:'/aaa', // ---路由起别名命名---
},
]
})
// 4、抛出路由对象
export default router;
路由组件传参
在组件中使⽤ $route 会使之与其对应路由形成⾼度耦合,从⽽使组件只能在某些特定的 URL 上使⽤,限制了其灵活性。使⽤ props 将组件和路解耦:
取代$route的耦合
{
path: '/user/:id',
name: 'user',
component: User,
props:true
},
src/views/User.vue
<template>
<div>
<h3>⽤户⻚⾯{{$route.params.id}}</h3>
<h3>⽤户⻚⾯{{id}}</h3>
</div>
</template>
<script>
export default{
//....
props: {
id: {
type: String,
default: ''
},
},
}
</script>
编程式导航
除了使⽤
注意:
在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。
声明式 | 编程式 |
---|---|
<route-link :to="..."> |
route.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()
this.router.go(1)
// 后退⼀步记录,等同于 history.back()
this.router.go(-1)
// 前进 3 步记录
this.router.go(3)
// 如果 history 记录不够⽤,那就默默地失败呗
this.router.go(-100)
this.router.go(100)
嵌套路由
实际⽣活中的应⽤界⾯,通常由多层嵌套的组件组合⽽成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件
/user/1/profile /user/1/posts
+------------------+ +-----------------
+
| User | | User |
|
| +--------------+ | | +-------------+
| |
| Profile | | +------------> | | Posts | |
|
| | | | | | |
|
|+-------------- + | | +-------------+ |
|
+------------------+ +-----------------
src/router/index.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
}
]
}
src/App.vue
<template>
<div id='app'>
<!-- 嵌套理由 -->
<router-link to="/user/1/profile">User/profile</router-link> |
<router-link to="/user/1/posts">User/posts</routerlink> |
</div>
</template>
在User组件的模版添加一个<router-view>
<template>
<div>
<h3>⽤户⻚⾯{{$route.params.id}}</h3>
<h3>⽤户⻚⾯{{id}}</h3>
<!--子路由的组件出口-->
<router-view></router-view>
</div>
</template>
命名视图
有时候想同时 (同级) 展示多个视图,⽽不是嵌套展示,例如创建⼀个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上⽤场了 。
src/router/index.js
{
path: '/home',
name: 'home',
//注意这个key是components
components: {
default: Home, //默认的名字
main: ()=>import('@/views/Main.vue'),
sidebar: () => import('@/views/Sidebar.vue')
}
},
src/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
route.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
src/router/index.js
{
path:'eaditor',
name:'eaditor',
component:()=>import('@/views/Eaditor')
},
src/App.vue
添加
<router-link :to="{name:'eaditor'}">编辑</router-link>
src/views/Eaditor.vue
<template>
<div>
<h2>用户的编辑页面</h2>
<textarea v-model='content' cols="30" rows="10"></textarea>
<button @click='saveContent'>保存</button>
<ul>
<li v-for='(item,index) in list' :key='index'>
<h3>{{item.title}}</h3>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
content: '',
list: [],
}
},
methods: {
saveContent() {
this.list.push({
title:this.content
})
//清空文本的输入框
this.content = '';
}
},
beforeRouteLeave(to,form,next){
if(this.content){
// 用户体验
alert('请确保保存信息之后离开');
next(false);
}else{
next();
}
}
}
</script>
<style lang="scss" scoped>
</style>
路由元信息实现权限控制
给需要添加权限的路由设置meta字段
src/router/index.js
{
path: '/blog',
name: 'blog',
component: ()=>import('@/views/Blog'),
meta:{
// 把blog加到黑名单
requireAuth:true
}
},
src/view/Blog.vue
<template>
<div>
<h3>我的博客页面</h3>
</div>
</template>
<script>
export default {
}
</script>
<style lang="scss" scoped>
</style>
src/App.vue
<router-link :to="{name:'blog'}">博客</router-link>
src/main.js
// 全局守卫
router.beforeEach((to, from, next)=>{
if(to.matched.some(record=>record.meta.requireAuth)){
// 需要权限,在黑名单
if(localStorage.getItem('user')){
next({
path: '/login',
query: {
redirect:to.fullPath //fullPath完整路径
}
})
}else{
next();
}
}
// 下面的放行全部是在白名单
next();
})
src/vies/Login.vue
<template>
<div>
<h2>登陆页面</h2>
<input type="text" v-model='user'>
<input type="password" v-model='pwd'>
<button @click='handleLogin'>登陆</button>
</div>
</template>
<script>
export default {
data() {
return {
user: '',
pwd: '',
}
},
methods: {
handleLogin() {
// 1、获取用户名和秘密
// 2、与后端发生交互
setTimeout(() => {
let data ={
// user:this.user
username: this.username
};
//保存用户名到本地
localStorage.setItem("user",JSON.stringify(data));
// 跳转到我的笔记
this.$router.push({
path:this.$route.query.redirect
});
}, 1000);
}
},
}
</script>
<style lang="scss" scoped>
</style>
数据获取
有时候,进⼊某个路由后,需要从服务器获取数据。例如,在渲染⽤户信息时,你需要从服务器获取⽤户的数据。我们可以通过两种⽅式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件⽣命周期
钩⼦中获取数据。在数据获取期间显示“加载中”之类的指示。 - 导航完成之前获取:导航完成前,在路由进⼊的守卫中获取数
据,在数据获取成功后执⾏导航。
导航完成之后获取数据
当你使⽤这种⽅式时,我们会⻢上导航和渲染组件,然后在组件的 created 钩⼦中获取数据。这让我们有机会在数据获取期间展示⼀个 loading 状态,还可以在不同视图间展示不同的 loading 状态。
Vuex
Vuex 是⼀个专为 Vue.js 应⽤程序开发的状态管理模式。它采⽤集中式存储管理应⽤的所有组件的状态,并以相应的规则保证状态以⼀种可预测的⽅式发⽣变化