Vuex-router
turanaishangn vuevuexrouterjs
# Vuex
# 原理
# state
- vuex 管理的状态对象
- 它应该是唯一的
# mutations
- 包含多个直接更新state 的方法(回调函数)的对象
- 谁来触发: action 中的commit(‘mutation 名称’)
- 只能包含同步的代码, 不能写异步代码
# actions
- 包含多个事件回调函数的对象
- 通过执行: commit()来触发mutation 的调用, 间接更新state
- 谁来触发: 组件中: $store.dispatch(‘action 名称’, data1) // ‘zzz’
- 可以包含异步代码(定时器, ajax)
# getters
- 包含多个计算属性(get)的对象
- 谁来读取: 组件中: $store.getters.xxx
# mapState,mapGetters
让代码简洁
<template>
<div>
<h1>当前的求和为:{{ sum }}</h1>
<h1>和放大10倍:{{ bigSum }}</h1>
<h1>我的名字是{{ name }},今年{{ age }}了</h1>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
- 引入mapState,mapGetters:
import { mapState,mapGetters } from "vuex";
- 计算属性:
computed: {
//借助mapState生成计算属性,从state中读取数据(对象写法)
// ...mapState({ sum: "sum", myName: "name", myAge: "age" }),
//数组写法
...mapState(['sum','name','age']),
// bigSum() {
// return this.$store.getters.bigSum;
// },
//借助mapGetters生成计算属性,从getters中读取数据(对象写法)
...mapGetters({ bigSum: "bigSum"}),
//数组写法
...mapGetters(['bigSum'])
},
# mapMutations, mapActions
引入
import { mapState,mapGetters,mapMutations, mapActions } from "vuex";
让代码简洁
体现在方法中
methods: {
// increment() {
// this.$store.commit("JIA", this.n);
// },
// decrement() {
// this.$store.commit("JIAN", this.n);
// },
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations
...mapMutations({increment:"JIA",decrement:"JIAN"}),
// incrementOdd() {
// this.$store.dispatch("jiaOdd", this.n);
// },
// incrementWait() {
// this.$store.dispatch("jiaWait", this.n);
// },
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions
...mapActions({incrementOdd:"jiaOdd",incrementWait:"jiaWait"})
},
# 模块化+命名空间
- 目的:让代码更好维护,让多种数据分类更加明确。
- 修改
store.js
const countAbout = {
namespaced:true,//开启命名空间
state:{x:1},
mutations: { ... },
actions: { ... },
getters: {
bigSum(state){
return state.sum * 10
}
}
}
const personAbout = {
namespaced:true,//开启命名空间
state:{ ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
countAbout,
personAbout
}
})
- 开启命名空间后,组件中读取state数据:
//方式一:自己直接读取
this.$store.state.personAbout.list
//方式二:借助mapState读取:
...mapState('countAbout',['sum','school','subject']),
- 开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取
this.$store.getters['personAbout/firstPersonName']
//方式二:借助mapGetters读取:
...mapGetters('countAbout',['bigSum'])
- 开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch
this.$store.dispatch('personAbout/addPersonWang',person)
//方式二:借助mapActions:
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
- 开启命名空间后,组件中调用commit
//方式一:自己直接commit
this.$store.commit('personAbout/ADD_PERSON',person)
//方式二:借助mapMutations:
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
# demo1: 计数器
在src目录下创建store/index.js
npm i vuex@3下载Vuex插件
实现页面展示
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
store,
}).$mount('#app')
index.js
import Vue from 'vue'
import Vuex from 'vuex'
import { nanoid } from 'nanoid'
import axios from 'axios'
Vue.use(Vuex)
//该文件用于创建Vuex中最核心的store
//准备action 用于相应组件中的动作
const actions = {
// jia(context,value){
// context.commit('JIA',value)
// },
// jian(context,value){
// context.commit('JIAN',value)
// },
jiaOdd(context,value){
if(context.state.sum % 2){
context.commit('JIA',value)
}
},
jiaWait(context,value){
setTimeout(() => {
context.commit('JIA',value)
}, 1000);
},
addObj(context){
axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
respose => {
context.commit('ADD_PERSON',respose.data)
},
error => {
console.log(error.message)
}
)
}
}
//准备mutations 用于操作数据
const mutations = {
JIA(state,value){
state.sum += value
},
JIAN(state,value){
state.sum -= value
},
ADD_PERSON(state,value){
if(value != '' & isNaN(value)){
const personObj = {id:nanoid(),name:value}
state.personList.unshift(personObj)
}
}
}
//准备state 用于存储数据
const state = {
sum: 0,
name:'郝佳瑶',
age:22,
personList:[
{id:17,name:'李四'}
]
}
//准备state 用于数据加工
const getters = {
bigSum(state){
return state.sum*10
}
}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state,
getters
})
count.vue
<template>
<div>
<h1>当前的求和为:{{ sum }}</h1>
<h1>和放大10倍:{{ bigSum }}</h1>
<h1>我的名字是{{ name }},今年{{ age }}了</h1>
<h1>Person组件的总人数是:{{personList.length}}</h1>
<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment(n)">+</button>
<button @click="decrement(n)">-</button>
<button @click="incrementOdd(n)">当前求和为奇数再加</button>
<button @click="incrementWait(n)">等一等再加</button>
</div>
</template>
<script>
import { mapState,mapGetters,mapMutations, mapActions } from "vuex";
export default {
name: "myCount",
data() {
return {
n: 1,
};
},
methods: {
// increment() {
// this.$store.commit("JIA", this.n);
// },
// decrement() {
// this.$store.commit("JIAN", this.n);
// },
//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations
...mapMutations({increment:"JIA",decrement:"JIAN"}),
// incrementOdd() {
// this.$store.dispatch("jiaOdd", this.n);
// },
// incrementWait() {
// this.$store.dispatch("jiaWait", this.n);
// },
//借助mapActions生成对应的方法,方法中会调用dispatch去联系actions
...mapActions({incrementOdd:"jiaOdd",incrementWait:"jiaWait"})
},
computed: {
//借助mapState生成计算属性,从state中读取数据(对象写法)
// ...mapState({ sum: "sum", myName: "name", myAge: "age" }),
//数组写法
...mapState(['sum','name','age','personList']),
// bigSum() {
// return this.$store.getters.bigSum;
// },
//借助mapGetters生成计算属性,从getters中读取数据(对象写法)
//...mapGetters({ bigSum: "bigSum"}),
//数组写法
...mapGetters(['bigSum'])
},
};
</script>
<style>
button {
margin-left: 10px;
}
</style>
person.vue
<template>
<div>
<h1>Count组件的sum是:{{sum}}</h1>
<input type="text" v-model="name" />
<button @click="add(name)">添加</button>
<button @click="addObj">随即添加</button>
<ul>
<li v-for="p in personList" :key="p.id">{{p.name}}</li>
</ul>
</div>
</template>
<script>
import { mapState,mapMutations,mapActions } from 'vuex';
export default {
name: "myPersons",
data() {
return {
name: "",
};
},
computed:{
...mapState(['personList','sum'])
},
methods: {
...mapMutations({add:"ADD_PERSON"}),
...mapActions(['addObj'])
},
};
</script>
<style>
</style>
# Vue-router
下载路由
npm i vue-router@3
npm i vue-router
# 基本路由
# 基本效果
# 注册路由器
main.js
import Vue from 'vue'
import App from './App.vue'
import VueRouter from 'vue-router'
import router from './router'
Vue.config.productionTip = false
Vue.use(VueRouter)
new Vue({
render: h => h(App),
router
}).$mount('#app')
# 路由器模块: src/router/index.js
//该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import myAbout from '../pages/About.vue'
import myHome from '../pages/Home.vue'
//创建并暴露一个路由器
export default new VueRouter({
routes: [
{ path: '/about', component: myAbout },
{ path: '/home', component: myHome },
]
})
# 应用组件: App.vue
<template>
<div id="app">
<div class="row">
<my-banner/>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- 原始html中我们使用a标签来实现跳转 -->
<!-- <a class="list-group-item" active href="./about.html">About</a>
<a class="list-group-item" href="./homr.html">About</a> -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import myBanner from './components/Banner.vue'
export default {
name: 'App',
components: {
myBanner,
}
}
</script>
# 路由组件
存放在pages里面
About.vue
<template>
<div>
<h2>我是About的内容</h2>
</div>
</template>
<script>
export default {
name:'myAbout'
}
</script>
Home.vue
<template>
<div>
<h2>我是Home的内容</h2>
</div>
</template>
<script>
export default {
name:'myHome'
}
</script>
# 普通组件
Banner.vue
<template>
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header">
<h2>Vue Router Demo</h2>
</div>
</div>
</template>
<script>
export default {
name:'myBanner'
}
</script>
# 嵌套路由
# 显示效果
# 结构
# 路由组件
Home.vue修改
<template>
<div>
<h2>Home</h2>
<div>
<ul class="nav nav-tabs">
<li>
<router-link to="/home/news">News</router-link>
<router-link to="/home/message">Message</router-link>
</li>
</ul>
<div>
<router-view></router-view>
<hr />
</div>
</div>
</div>
</template>
<script>
export default {
name:'myHome'
}
</script>
新增组件New.vue Message.vue
Message.vue
<template>
<div>
<ul>
<li v-for="message in messages" :key="message.id">
<a href="#">{{ message.title }}</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name: "myMessage",
data() {
return {
messages: [],
};
},
mounted() {
//模拟ajax请求从后台获取数据
setTimeout(() => {
const messages = [
{
id: 1,
title: "message001",
},
{
id: 2,
title: "message002",
},
{
id: 3,
title: "message003",
},
];
this.messages = messages;
}, 1000);
},
};
</script>
New.vue
<template>
<div>
<ul>
<li v-for="(news, index) in newsArr" :key="index">{{ news }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "myNews",
data() {
return {
newsArr: ["news001", "news002", "news003", "news004"],
};
},
};
</script>
# index.js修改
index.js
//该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import myAbout from '../pages/About.vue'
import myHome from '../pages/Home.vue'
import myNews from '../pages/News.vue'
import myMessage from '../pages/Message.vue'
//创建并暴露一个路由器
export default new VueRouter({
routes: [{
path: '/about',
component: myAbout
},
{
path: '/home',
component: myHome,
children: [{
path: 'news',
component: myNews
},
{
path: 'message',
component: myMessage
},
]
},
]
})
# 路由的query参数
# 传递参数
<!-- 跳转并携带query参数,to的字符串写法 -->
<router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
<!-- 跳转并携带query参数,to的对象写法 -->
<router-link
:to="{
path:'/home/message/detail',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
# 接收参数
$route.query.id
$route.query.title
# 命名路由
- 作用:可以简化路由的跳转。
- 如何使用 给路由命名
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' //给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
简化跳转:
# 路由的params参数
配置路由,声明接收params参数
{
path:'/home',
component:Home,
children:[
{
path:'news',
component:News
},
{
component:Message,
children:[
{
name:'xiangqing',
path:'detail/:id/:title', //使用占位符声明接收params参数
component:Detail
}
]
}
]
}
传递参数
<!-- 跳转并携带params参数,to的字符串写法 -->
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
<!-- 跳转并携带params参数,to的对象写法 -->
<router-link
:to="{
name:'xiangqing',
params:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数:
$route.params.id
$route.params.title
# 路由的props配置
作用:让路由组件更方便的收到参数
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// props:true
//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
props($route) {
return {
id: $route.query.id,
title:$route.query.title,
a: 1,
b: 'hello'
}
}
}
跳转去组件的具体代码
<template>
<ul>
<h1>Detail</h1>
<li>消息编号:{{id}}</li>
<li>消息标题:{{title}}</li>
<li>a:{{a}}</li>
<li>b:{{b}}</li>
</ul>
</template>
<script>
export default {
name: 'Detail',
props: ['id', 'title', 'a', 'b'],
mounted () {
console.log(this.$route);
}
}
</script>
# <router-link>的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
- 如何开启replace模式:<router-link replace .......>News
# 编程式路由导航
- 作用:不借助
<router-link>实现路由跳转,让路由跳转更加灵活 - 具体编码:
//$router的两个API
this.$router.push({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.replace({
name:'xiangqing',
params:{
id:xxx,
title:xxx
}
})
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退
# 缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁。
- 具体编码: 这个 include 指的是组件名
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
# 两个新的生命周期钩子
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。 具体名字:
activated路由组件被激活时触发。deactivated路由组件失活时触发。
这两个生命周期钩子需要配合前面的缓存路由组件使用(没有缓存路由组件不起效果)
<template>
<div>
<ul>
<li :style="{ opacity }">郝佳瑶</li>
<li v-for="(news, index) in newsArr" :key="index">{{ news }}</li>
</ul>
</div>
</template>
<script>
export default {
name: "myNews",
data() {
return {
newsArr: ["news001", "news002", "news003", "news004"],
opacity: 1,
};
},
activated() {
console.log("News组件被激活了");
this.timer = setInterval(() => {
console.log("@");
this.opacity -= 0.01;
if (this.opacity <= 0) this.opacity = 1;
}, 16);
},
deactivated() {
console.log("News组件失活了");
clearInterval(this.timer);
},
};
</script>
# 路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫、独享守卫、组件内守卫
# 全局守卫
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'zhejiang'){ //权限控制的具体规则
next() //放行
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next() //放行
}
})
//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
console.log('afterEach',to,from)
if(to.meta.title){
document.title = to.meta.title //修改网页的title
}else{
document.title = 'vue_test'
}
})
完整代码
// 这个文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
// 引入组件
import About from '../pages/About.vue'
import Home from '../pages/Home.vue'
import Message from '../pages/Message.vue'
import News from '../pages/News.vue'
import Detail from '../pages/Detail.vue'
// 创建并暴露一个路由器
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home,
meta:{title:'主页'},
children: [
{
path: 'news',
component: News,
meta:{isAuth:true,title:'新闻'}
},
{
path: 'message',
name: 'mess',
component: Message,
meta:{isAuth:true,title:'消息'},
children: [
{
path: 'detail/:id/:title',
name: 'xiangqing',
component: Detail,
meta:{isAuth:true,title:'详情'},
props($route) {
return {
id: $route.query.id,
title:$route.query.title,
a: 1,
b: 'hello'
}
}
}
]
}
]
},
{
path: '/about',
component: About,
meta:{ title: '关于' }
}
]
})
// 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to, from, next) => {
console.log('前置路由守卫', to, from);
if(to.meta.isAuth) {
if(localStorage.getItem('school') === 'zhejiang') {
// 放行
next()
} else {
alert('学校名不对,无权查看')
}
} else {
next()
}
})
// 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to, from) => {
console.log('后置路由守卫', to, from)
document.title = to.meta.title || '我的系统'
})
export default router
# 独享守卫
就是在 routes 子路由内写守卫
beforeEnter(to,from,next){
console.log('beforeEnter',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){
next()
}else{
alert('暂无权限查看')
// next({name:'guanyu'})
}
}else{
next()
}
}
# 组件内守卫
在具体组件内写守卫
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
# 路由器的两种工作模式
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
hash模式:
地址中永远带着#号,不美观 。 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
兼容性较好。
history模式:
地址干净,美观 。 兼容性和hash模式相比略差。 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。