Vuex-router

vuevuexrouterjs

# Vuex

# 原理

# state

  1. vuex 管理的状态对象
  2. 它应该是唯一的

# mutations

  1. 包含多个直接更新state 的方法(回调函数)的对象
  2. 谁来触发: action 中的commit(‘mutation 名称’)
  3. 只能包含同步的代码, 不能写异步代码

# actions

  1. 包含多个事件回调函数的对象
  2. 通过执行: commit()来触发mutation 的调用, 间接更新state
  3. 谁来触发: 组件中: $store.dispatch(‘action 名称’, data1) // ‘zzz’
  4. 可以包含异步代码(定时器, ajax)

# getters

  1. 包含多个计算属性(get)的对象
  2. 谁来读取: 组件中: $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>
  1. 引入mapState,mapGetters:
import { mapState,mapGetters } from "vuex";
  1. 计算属性:
  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"})
  },

# 模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。
  2. 修改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
     }
   })
  1. 开启命名空间后,组件中读取state数据:
   //方式一:自己直接读取
   this.$store.state.personAbout.list
   //方式二:借助mapState读取:
   ...mapState('countAbout',['sum','school','subject']),
  1. 开启命名空间后,组件中读取getters数据:
   //方式一:自己直接读取
   this.$store.getters['personAbout/firstPersonName']
   //方式二:借助mapGetters读取:
   ...mapGetters('countAbout',['bigSum'])
  1. 开启命名空间后,组件中调用dispatch
   //方式一:自己直接dispatch
   this.$store.dispatch('personAbout/addPersonWang',person)
   //方式二:借助mapActions:
   ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  1. 开启命名空间后,组件中调用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

# 命名路由

  1. 作用:可以简化路由的跳转。
  2. 如何使用 给路由命名
{
	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>
  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. 如何开启replace模式:<router-link replace .......>News

# 编程式路由导航

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活
  2. 具体编码:
//$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() //可前进也可后退

# 缓存路由组件

  1. 作用:让不展示的路由组件保持挂载,不被销毁。
  2. 具体编码: 这个 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>

# 路由守卫

  1. 作用:对路由进行权限控制
  2. 分类:全局守卫、独享守卫、组件内守卫

# 全局守卫

//全局前置守卫:初始化时执行、每次路由切换前执行
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) {
}

# 路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

  3. hash模式:

     地址中永远带着#号,不美观 。
     若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
    

兼容性较好。

  1. history模式:

     地址干净,美观 。
     兼容性和hash模式相比略差。
     应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。