vue -【nextTick】-【过度与动画】-【插槽】-【配置代理服务器】-【vuex】-【路由】

nextTick

一、语法:this.$nextTick(回调函数)
二、作用:在下一次DOM更新结束后执行其指定的回调。
三、什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
四、需求:点击【编辑】按钮后,出现编辑输入框,并且输入框已经获焦。
五、思路:由todo.isEdit变量来控制输入框的展示和隐藏:<input type="text" v-show="todo.isEdit">,点击【编辑】按钮后,执行handleEdit函数,todo.isEdit变量变为true,并让输入框获焦,其余情况todo.isEdit变量为false,隐藏输入框,handleEdit函数代码如下:

handleEdit(todo){
	if(todo.hasOwnProperty('isEdit')){// 看todo对象上有没有isEdit属性,有的话置true
		todo.isEdit = true
	}else{// 没有的话,添加并置true
		this.$set(todo,'isEdit',true)
	}
	this.$refs.inputTitle.focus()
}

这样写代码根本无法实现点击【编辑】按钮后,出现编辑输入框,此时编辑输入框已经获焦这一需求。因为vue并不是像我们想的那样,发现你改数据了,立马帮你重新解析模板,他会等handleEdit中的代码都执行完了,再去重新解析模板。如果每次改了数据都去重新解析模板会拉低效率,所以vue等handleEdit中的代码都执行完了,再一起去更改数据,解析模板,所以上面执行了todo.isEdit = true后,页面上并没有出现input框,然后就执行this.$refs.inputTitle.focus()获取焦点,你现在压根拿不到input框,你也无法实现聚焦。
下面使用nextTick来解决这一问题

handleEdit(todo){
	// 对象.hasOwnProperty('属性名'):对象身上是否有某个属性
	if(todo.hasOwnProperty('isEdit')){// 有isEdit这个属性,改变它的值
		todo.isEdit = true
	}else{// 没有isEdit这个属性,新增
		this.$set(todo,'isEdit',true)
	}
    //组件.$nextTick(回调函数):回调函数会在DOM节点更新后调用
	this.$nextTick(function(){
		this.$refs.inputTitle.focus()// 获取焦点
	})
},

过度与动画

一、作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
二、写法:

  1. 准备好样式:
    元素进入的样式:
    (1)v-enter:进入的起点
    (2)v-enter-active:进入过程中
    (3)v-enter-to:进入的终点
    元素离开的样式:
    (1)v-leave:离开的起点
    (2)v-leave-active:离开过程中
    (3)v-leave-to:离开的终点
  2. 使用<transition>包裹要过度的元素,也就是说,你想让谁发生动画效果,你就把谁用transition包裹起来,并为<transition>标签配置name属性:
<transition name="hello">
	<h1 v-show="isShow">你好啊!</h1>
</transition>

如果不指定name="hello"属性,那么vue会在合适的时机给transition中的元素添加类名:.v-enter-active;如果指定了,那么vue会在合适的时机给transition中的元素添加类名:.hello-leave-active等。如果有多个元素有动画效果,那么一定要指定name属性,不然两个元素的动画就一样了

  1. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

三、将transition标签的appear设为true:<transition :appear="true"></transition>表明出现时就激活动画

 <transition appear="true"></transition>:transition标签中有个appear属性,其值为`"true"`(字符串类型)
<transition appear></transition>:让transition标签有appear属性

四、transition中的元素最终不会形成真正的元素,vue编译时会把transition标签去掉
五、transition其实就只是在特定时候给标签添加类名,该类名具体做什么过度/动画,都是程序员决定的

<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition name="hello" appear>
			<h1 v-show="isShow">你好啊!</h1>
		</transition>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}

	.hello-enter-active{// 进入时要激活的样式
		animation: atguigu 0.5s linear;
	}

	.hello-leave-active{// 离开时要激活的样式
		animation: atguigu 0.5s linear reverse;
	}

    //动画要使用@keyframes定义个关键帧
	@keyframes atguigu {//@keyframes 动画名
    //动画过程
		from{
			transform: translateX(-100%);
		}
		to{
			transform: translateX(0px);
		}
	}
</style>

<transition>标签只能用在单个元素上,如果你有多个元素要实现动画效果,使用<transition-group>标签,<transition-group>标签中的每个元素都要有个唯一的key值

<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition-group name="hello" appear>
			<h1 v-show="!isShow" key="1">你好啊!</h1>
			<h1 v-show="isShow" key="2">尚硅谷!</h1>
		</transition-group>
	</div>
</template>

<script>
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}
	/*
	进入的起点、离开的终点
	只在进入的起点(第一帧)加.hello-enter,第二帧.hello-enter就被移除了,这个过程很快,我们很难捕捉到
	*/
	.hello-enter,.hello-leave-to{
		transform: translateX(-100%);
	}
  	//整个进入过程、整个离开过程
	.hello-enter-active,.hello-leave-active{
		transition: 0.5s linear;
	}
	/*
	进入的终点、离开的起点
	只在离开的起点(第一帧)加.hello-leave,第二帧.hello-leave就被移除了,这个过程很快,我们很难捕捉到
	*/
	.hello-enter-to,.hello-leave{
		transform: translateX(0);
	}
</style>

在vue中可以使用第三方成型的样式库/动画库来辅助我们快速实现炫酷的动画效果,如animate.css

  1. 在项目终端安装:npm install animate.css
  2. 引入:import 'animate.css'//因为引入的是样式,不是JS模块,所以直接在组件中写:import 路径
  3. <transition><transition-group>标签添加:name="animate__animated animate__bounce"
  4. <transition><transition-group>标签添加:
    (1)进入的动画:enter-active-class="animate__swing"
    (2)离开的动画:leave-active-class="animate__backOutUp"
    其中,animate__swing、animate__backOutUp都是在animate.css中找的
<template>
	<div>
		<button @click="isShow = !isShow">显示/隐藏</button>
		<transition-group 
			appear
			name="animate__animated animate__bounce" 
			enter-active-class="animate__swing"
			leave-active-class="animate__backOutUp"
		>
			<h1 v-show="!isShow" key="1">你好啊!</h1>
			<h1 v-show="isShow" key="2">尚硅谷!</h1>
		</transition-group>
	</div>
</template>

<script>
	import 'animate.css'//引入,因为引入的是样式,不是JS模块,所以直接写:import 路径
	export default {
		name:'Test',
		data() {
			return {
				isShow:true
			}
		},
	}
</script>

<style scoped>
	h1{
		background-color: orange;
	}
</style>

vue脚手架配置代理服务器

一、方法一:在vue.config.js中添加如下配置:

devServer:{
  proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

二、方法二:编写vue.config.js配置具体代理规则:

module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

vue.config.js中代码如下:

//本文件用于修改脚手架工作模式
//vue最终会把vue.config.js输送给webpack,webpack是基于Node的,所以vue.config.js中使用commonJS。
//配置方式参考:https://cli.vuejs.org/zh/config/
module.exports = {
  pages: {
    index: {
      entry: 'src/main.js',//入口
    },
  },
	lintOnSave:false, //关闭语法检查
	/*
	参考:https://cli.vuejs.org/zh/config/#devserver-proxy
	开启代理服务器(方式一)
	缺点:
	    1.不能配置多个代理;
	    2.不能灵活的控制代理是否转发请求,
	        你请求的资源是:http://localhost:8080/students,public是本服务器的根文件夹,即http://localhost:8080,
            如果public/students存在,那么代理服务器会直接返回该资源,而不会发起请求。
            但是如果我们希望代理服务器转发请求,这种配置方式无法做到。当你请求的资源不存在时,
            无法控制他是否转发请求,他一定会转发请求。
	 */
	/* devServer: {
    proxy: 'http://localhost:5000'//一会儿把请求转发给谁
  }, */

	//开启代理服务器(方式二)
	devServer: {
    proxy: {
      /*
      '/atguigu'和'/demo'代表请求前缀,请求前缀不匹配(没有/atguigu或/demo)则不转发请求
      前缀紧跟在端口号后面
       */
      '/atguigu': {// 请求前缀为/atguigu,即http://localhost:8080/atguigu/students,则配置如下:
        target: 'http://localhost:5000',//一会儿把请求转发给谁
                /*
                 代理服务器和浏览器都在http://localhost:8080,
                 假设现在App.vue中发起的请求为http://localhost:8080/atguigu/students,
                 这个请求是发给代理服务器的,代理服务器收到后,发现前缀是/atguigu,
                 会把这个请求转发给http://localhost:5000,
                 即代理服务器发起的请求是:http://localhost:5000/atguigu/students,会带上前缀,
                 如果我们不希望他带上前缀,则需要配置pathRewrite
                 */
				pathRewrite:{'^/atguigu':''},//把所有以atguigu开头的路径变成''
        // ws: true, //用于支持websocket,默认为true,在react中默认为false
        // changeOrigin: true //用于控制请求头中的host值(请求来自于哪里),默认为true,在react中默认为false。
          // 为false则暴露自己(代理服务器)真实的url(http://localhost:8080),
          // 为true则隐藏自己(代理服务器)真实的url,显示服务器的url(http://localhost:5000)
      },
      '/demo': {
                target: 'http://localhost:5001',
				pathRewrite:{'^/demo':''},
        // ws: true, //用于支持websocket
        // changeOrigin: true //用于控制请求头中的host值
      }
    }
  }
}

使用axios发送网络请求

  1. 在项目终端安装axios:npm i axios
  2. 在使用的组件中引入:import axios from 'axios'

插槽

作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 => 子组件
分类:默认插槽、具名插槽、作用域插槽

默认插槽

子组件中:

<template>
    <div>
       <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
       <slot>插槽默认内容...</slot>
    </div>
</template>

父组件(使用者)中:

<Category>
    <!-- 这个img标签是在本组件完成解析后再塞到Category组件中,所以你如果想给img标签添加样式,应该在本组件添加 -->
	<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
</Category>

具名插槽

子组件中:

<template>
	<div>
    	<!--
		定义一个插槽(挖个坑,等着组件的使用者进行填充)
		如果组件中有多个插槽,要给每个插槽设置name属性
		-->
        <slot name="center">插槽默认内容...</slot>
        <slot name="footer">插槽默认内容...</slot>
    </div>
</template>

父组件(使用者)中:

  1. 方法一:通过slot属性指定放入哪个插槽
<Category title="美食" >
	<img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
	<a slot="footer" href="http://www.atguigu.com">更多美食</a>
	</Category>
  1. 方法二:在<template>标签中指定插入哪个插槽可以用v-slot:footer代替slot='footer'
<Category>
    <template slot="center">
      <div>html结构1</div>
    </template>

    <template v-slot:footer>
       <div>html结构2</div>
    </template>
</Category>

作用域插槽

一、理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
二、作用域插槽的应用:数据不在你这儿,在Category组件中,你要用数据你又拿不到数据。那么可以:

  1. 在Category组件中定义插槽,在插槽中把数据传过去
    子组件中:
<template>
	<div>
    	<!--
		数据在组件中,根据数据所生成的结构由组件的使用者决定
		下面一行代码会把games和msg传给插槽的使用者
		-->
		<slot :games="games" msg="hello">我是默认的一些内容</slot>
	</div>
</template>
	
<script>
	export default {
    	name:'Category',
        props:['title'],
        //数据在子组件自身
        data() {
        	return {
            	games:['红色警戒','穿越火线','劲舞团','超级玛丽']
            }
        },
	}
</script>
  1. 使用插槽的组件将要放在插槽中的内容用template标签包裹,template标签指定scope属性接收插槽传过来的数据,有以下两种方法指定scope的值:scope="atguigu"或scope="{games}",也可以为template标签指定slot-scope属性接收插槽传过来的数据:slot-scope="{games}"。如果Category组件中使用具名插槽的话,需要在template标签中使用slot指定插槽名
    父组件中:
<template>
	<div class="container">
		<Category title="游戏">
      <!--
      ul标签及其中的内容是你要放在插槽中的内容,他们的外面必须用template标签包裹,
      template标签指定scope属性接收插槽传过来的数据。template标签的atguigu变量会收到插槽传过来的数据,
      atguigu收到的是个对象,里面存放着键值对,键是插槽传过来的变量名,值是数据。
      即:atguigu={'games':['红色警戒','穿越火线','劲舞团','超级玛丽'], 'msg':"hello"}
      -->
			<template scope="atguigu">
				<ul>
					<li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li>
				</ul>
			</template>
		</Category>

		<Category title="游戏">
			<template scope="{games}">
				<ol>
					<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
				</ol>
			</template>
		</Category>

		<Category title="游戏">
			<template slot-scope="{games}">
				<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
			</template>
		</Category>
	</div>
</template>

<script>
	import Category from './components/Category'
	export default {
		name:'App',
		components:{Category},
	}
</script>

<style scoped>
	.container,.foot{
		display: flex;
		justify-content: space-around;
	}
	h4{
		text-align: center;
	}
</style>

vuex

一、概念:在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
二、何时使用:多个组件需要共享数据时
三、原理:vuex中数据保存在state中,state是个对象,如果某个组件要操作数据,那么组件调用dispatch('要进行的动作类型(比如加)',你要加几),然后你要加的动作和你要加的值都被传递到了actions中,actions也是个对象,里面存放键值对,里面肯定有一个键是你要操作的动作类型,值是个函数,传递到actions后,只要你要进行的动作类型和actions中的某个键一致,就会触发该键的值(函数)的调用,并把你要加的数据作为参数传递给函数,函数中要调用commit('要进行的动作类型(比如加)',你要加几),然后你要加的动作和你要加的值都被传递到了mutations中,mutations也是个对象,里面存放键值对,里面肯定有一个键是你要操作的动作类型,值是个函数,传递到mutations后,只要你要进行的动作类型和mutations中的某个键一致,就会触发该键的值(函数)的调用,并把state和你要加的数据作为参数传递给函数,在函数中进行数据操作更改state中的数据,然后vue会重新解析组件并渲染,于是页面上的数据也发生了相应的改变。

这么一看,actions好像是多余的,其实不然,有这么一个场景:当你要进行一个动作,但是进行这个动作的值需要发送Ajax请求才能获取,你就需要再actions中发送请求。但是如果,你知道进行动作的值,也就是不需要发请求,那么在组件中可以直接调用commit('要进行的动作类型(比如加)',你要加几)操作state中的数据。

⚠️其实你在actions中可以直接操作state中的数据,但是不建议这么做,因为开发者工具监视的是mutations,你在actions中更改state中的数据,这个操作是不会被开发者捕获到的。vue开发者工具和mutations对话,是因为mutations才是真正帮你修改state中数据的。
四、state、actions、mutations都需要store的管理,因为dispatch、commit都是store提供的

搭建vuex环境

⚠️注意:在vue2中要使用vuex的3版本,在vue3中要使用vuex的4版本
一、在本项目终端安装:npm i vuex@3
二、引入:import Vuex from 'vuex'
三、使用:Vue.use(Vuex)

完成引入和使用,在创建vue实例时可以传入store配置项,配置好了后,vm及所有的vc身上都会有$store属性。

四、创建文件:src/store/index.js,index.js用于创建store。$store属性他没有值啊,所以我们在index.js中创建$store属性的值store。
五、index.js中定义actions、mutations、state三个对象,store管理这三个对象。
六、在index.js中使用new Vuex.Store({actions、mutations、state})创建并暴露store

//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}

//创建并暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state
}

七、在main.js中引入暴露的这个store,并在创建vue实例时传入store配置项并将上述store配置给$store属性

......
//引入store
import store from './store'
......

//创建vm
new Vue({
	el:'#app',
	render: h => h(App),
	store
})

引入一个文件,会先把引入的这个文件中的代码运行完了再执行后面的代码。在脚手架中引入文件,他会扫描整个文件的import语句,并将他们按照你编写代码的顺序汇总到最上方,所以就算你把所有import语句写在了代码最下方,其实也是先执行import语句,不管你在两个import语句之间写了什么代码,都是先执行两个import语句,再执行两个import语句中间的代码

基本使用:

一、初始化数据、配置actions、配置mutations,操作文件store.js

import Vue from 'vue'//引入Vue核心库
import Vuex from 'vuex'//引入Vuex
Vue.use(Vuex)//应用Vuex插件
//准备actions对象——响应组件中用户的动作
const actions = {
	// jia:function (){}可以简写为一个函数
	jia(context,value){
		//接收两个参数:第一个是context,它里面有commit、dispatch、state等,第二个是要操作的值
		//在context中给你commit是为了让你继续调用mutations,给你state是为了方便你根据目前的数据做判断,
		//给你dispatch是为了让你可以在actions中调用actions,如:context.dispatch('demo',value)
		console.log('actions中的jia被调用了')
		context.commit('JIA',value)
	}
}
//准备mutations对象——修改state中的数据,mutations中的方法名用大写,用来区分actions中的方法
const mutations = {
	JIA(state,value){
		//接收两个参数,第一个是state,第二个是要操作的值
		console.log('mutations中的JIA被调用了')
		state.sum += value
	}
}
//准备state对象——保存具体的数据
const state = {
	sum:0 //当前的和
}
//创建并暴露store
export default new Vuex.Store({
	actions,
	mutations,
	state
})

二、组件中读取vuex中的数据:$store.state.sum
三、组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)$store.commit('mutations中的方法名',数据)

若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

getters的使用

一、概念:state中的数据需要加工,并且加工数据的逻辑复杂并且逻辑需要复用,可以使用getters。
二、在store.js中追加getters配置

......
//准备getters——用于将state中的数据进行加工,类似于组件的computed
const getters = {
	bigSum(state){
		return state.sum*10// 一定要写返回值
	}
}

//创建并暴露store
export default new Vuex.Store({
	......
	getters
})

三、组件中读取数据:$store.getters.bigSum

四个map方法的使用

mapState方法

mapState方法:用于帮助我们映射state中的数据为计算属性
我们以前的代码是:

<h1>当前求和为:{{$store.state.sum}}</h1>
<h3>当前求和放大10倍为:{{$store.state.bigSum}}</h3>
<h3>我在{{$store.state.school}},学习{{$store.state.subject}}</h3>

每次我们需要用vuex中的数据时,都要写$store.state前缀,太麻烦了,我们可以用计算属性处理一下

computed:{
	//靠程序员自己亲自去写计算属性
	sum(){
		return this.$store.state.sum
	},
	school(){
		return this.$store.state.school
	},
	subject(){
		return this.$store.state.subject
	},
    bigSum(){
		return this.$store.getters.bigSum
	}
}

但是在计算属性中也写了很多的this.$store.state,我们可以引入vuex中的mapState来实现,我们只用给mapState传入计算属性的名字,以及要从state中读取的数据,他就会帮我们生成上述代码:

computed:{
	//借助mapState生成计算属性,从state中读取数据。(对象写法)
    //mapState接收一个对象,对象中是键值对,键是计算属性名,值是要从state中读取的数据,键值都是字符串,
   	//下面这行代码虽然表面上键是变量,但是vuex会将其进行处理,让他变成字符串形式。
	//如果值不加'',那么会被理解为变量
	// ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),

	//借助mapState生成计算属性,从state中读取数据。(数组写法)
    //生成的计算属性名和从state中读取的数据名一致,可以使用数组的简写方法,表示计算属性名为sum,读取state中的sum变量
	...mapState(['sum','school','subject'])
}

mapGetters方法

mapGetters方法:用于帮助我们映射getters中的数据为计算属性

computed: {
	/* bigSum(){
		return this.$store.getters.bigSum
	}, */
	
    //借助mapGetters生成计算属性:bigSum(对象写法)
    ...mapGetters({bigSum:'bigSum'}),

    //借助mapGetters生成计算属性:bigSum(数组写法)
    ...mapGetters(['bigSum'])
},

mapMutations方法

mapMutations方法:用于助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数
每次调用mutations都需要写this.$store.commit,太冗余。可以使用mapMutations让vuex帮我们生成以下代码:

increment(){
	this.$store.commit('JIA',this.n)
},

mapMutations的用法和mapState一致,mapMutations({increment:'JIA'})代表生成的方法名为increment,要调用的是JIA这个mutations
⚠️在使用increment时要把值作为参数传进来。

methods:{
    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
	...mapMutations({increment:'JIA',decrement:'JIAN'}),

	//借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
	// ...mapMutations(['JIA','JIAN']),
}

mapActions方法

mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数
可以使用mapActions让vuex帮我们生成:

incrementOdd(){
	this.$store.dispatch('jiaOdd',this.n)
}

mapActions的用法和mapMutations一致

methods:{
    //靠mapActions生成:incrementOdd、incrementWait(对象形式)
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

    //靠mapActions生成:incrementOdd、incrementWait(数组形式)
    ...mapActions(['jiaOdd','jiaWait'])
}

mapActions与mapMutations使用时,若需要传递参数,需要在模板中绑定事件时传递好参数,否则参数是事件对象。

模块化+命名空间

一、目的:让代码更好维护,让多种数据分类更加明确。
二、修改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'}),

这一部分详见代码

路由

一、理解: 一个路由(route)就是一组映射关系(key - value),key指的是路径,路由的value指的是相应的组件或函数。
二、value是函数这种情况通常出现在后端路由中,用于处理客户端提交的请求,即你是什么路径,我就给你调用指定的函数相应你本次请求。
三、前端路由:key是路径,value是组件。
四、多个路由route需要路由器(router)进行管理。
五、路由route是为了实现单页面应用中页面的切换
六、在单页面应用中,路由器router会时刻监视URL的变化,获取路径(即端口号后面的东西),程序员在router中配置了路由规则(一组key-value的对应关系),如果路径变成了xxx,就展示相应的组件,如果在路由规则中没有配置某路径,那么访问该路径时不会展示任何东西

基本使用

⚠️vue-router4只能在vue3中使用,vue-router3只能在vue2中使用
一、在项目终端安装vue-router:npm i vue-router@3
二、在main.js中引入:import VueRouter from 'vue-router'
三、在main.js中使用:Vue.use(VueRouter)
完成以上三步就可以在new Vue时配置router项,下面创建router项的值
四、新建router/index.js,在该文件中使用new VueRouter({配置项对象})创建路由器router并暴露

import VueRouter from 'vue-router'//引入VueRouter
//引入Luyou 组件
import About from '../components/About'
import Home from '../components/Home'

//创建并暴露一个路由器,去管理一组一组的路由规则。路由器router管理的一堆路由route,所以配置对象里是routes,期望routes是一个数组
export default new VueRouter({
	routes:[//在里面写一组一组的路由,每组路由都是一个配置对象
	//这是个配置对象,配置对象最大的特点就是里面的每一项都是按照设计好的来的,你不能在这里面添加稀奇古怪的属性
		{// 如果路径为about,展示About组件
			path:'/about',
			component:About
		},
		{// 如果路径为home,展示Home组件
			path:'/home',
			component:Home
		}
	]
})

五、在main.js中引入router

import router from './router'//引入路由器
new Vue({//创建vm
	el:'#app',
	render: h => h(App),
	router:router
})

原始html中我们使用a标签实现页面的跳转

<a class="list-group-item active" href="./about.html">About</a>
<a class="list-group-item" href="./home.html">Home</a>

Vue中借助router-link标签实现路由的切换,to属性指定点击后路径变为啥,router-link标签会被转成a标签
active-class指定该元素被激活时的样子,相当于告诉路由,一会儿有人点你的时候,你把active-class中的样式加到自己身上

<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>

指定展示位置:<router-view></router-view>
⚠️几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

多级路由/嵌套路由

一、配置路由规则,使用children配置项:

routes:[
	{
		path:'/about',
		component:About
	},
	{
		path:'/home',// 一级路由要加/
		component:Home,
		children:[// 配置Home路由下的子路由,也是个数组,因为Home路由下可能有很多个子路由
			{
				path:'news',// 某个一个路由的子路由不加/,因为人家底层在遍历规则时,已经给你加上/了,
				component:News,
			},
			{
				path:'message',//此处一定不要写:/message
				component:Message,
			}
		]
	}
]

跳转时,如果路由链接是二级路由或子级路由,必须带着父亲的路径,也就是写完整路径:<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>

路由的query参数

一、传递参数

  1. 跳转路由并携带query参数,to的字符串写法:
<!-- :to="`/home/message/detail?id=${m.id}&title=${m.title}`":使用v-bind,""里的东西被当作表达式执行,里面是一个模板字符串,还混着表达式,最终解析为:to="/home/message/detail?id=变量的值&title=变量的值" -->
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp;
  1. 跳转路由并携带query参数,to的对象写法
<router-link :to="{
	path:'/home/message/detail',
	query:{
		id:m.id,
		title:m.title
	}
}">
	{{m.title}}
</router-link>

二、接收参数:传过来的数据都放在了$route.query中

<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>

命名路由

一、作用:可以简化路由的跳转
二、如何使用:

  1. 给路由命名:
routes:[
	{
		name:'guanyu',//使用name属性给路由起名字,你给谁起名字,就给谁的配置项写name属性
		path:'/about',
		component:About
	},
	{
		path:'/home',
		component:Home,
		children:[
			{
				path:'news',
				component:News,
			},
			{
				path:'message',
				component:Message,
				children:[
					{
						name:'xiangqing',
						path:'detail',
						component:Detail,
					}
				]
			}
		]
	}
]
  1. 简化跳转
    简化前,需要写完整的路径:<router-link to="/demo/test/welcome">跳转</router-link>
    简化后,直接通过名字跳转:<router-link :to="{name:'hello'}">跳转</router-link>
    简化写法配合传递参数:如果是二级路由或三级路由,带的path就特别长,但是如果我们给路由配置了名字,就可以通过name指定,但是要配置name属性就必须把to写成个对象
<router-link :to="{
	// path:'/home/message/detail',
		name:'xiangqing',
		query:{
			id:m.id,
			title:m.title
		}
	}">
	{{m.title}}
</router-link>

路由的params参数

一、配置路由,声明接收params参数

{
	path:'/home',
	component:Home,
	children:[
		{
			path:'news',
			component:News
		},
		{
			component:Message,
			children:[
				{
					name:'xiangqing',
					//配置路由时,告诉他detail后面的是参数
					path:'detail/:id/:title', //使用占位符声明接收params参数
					component:Detail
				}
			]
		}	
	]
}

二、传递参数

  1. 跳转并携带params参数,to的字符串写法:<router-link :to="/home/message/detail/666/你好">跳转</router-link>
    不用写?,直接把参数拼进去,那这样路由可能就会把参数当成路径,所以我们在配置路由时,要在路径后面使用:指定占位符
  2. 跳转并携带params参数,to的对象写法,此时只能用name指定路由,不能用path:
<router-link 
	:to="{
		name:'xiangqing',// 如果携带的是params参数,这里只能用name指定路由,不能用path
		params:{
		   id:666,
            title:'你好'
		}
	}"
>跳转</router-link>

三、接收参数

<li>消息编号:{{$route.params.id}}</li>
<li>消息标题:{{$route.params.title}}</li>

路由的props配置

一、作用:让路由组件更方便的收到参数
二、 之前我们的代码:

<li>消息编号:{{$route.params.id}}</li>
<li>消息标题:{{$route.params.title}}</li>

每次都要写$route.params,太冗余,有没有一种办法让我们在这里直接写id或title?此时你可能想到用计算属性,但是计算属性里依旧没避免每个计算属性里都有this.$route.query,太多余,我们可以在配置路由时,给接收数据的路由配置props属性,详见router/index.js:

routes:[
	{
		path:'/home',
		component:Home,
		children:[
			{
				path:'message',
				component:Message,
				children:[
					{
						name:'xiangqing',
						path:'detail',
						component:Detail,

						//props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
						// props:{a:1,b:'hello'}// 传递的是死数据

						//props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,
						// 以props的形式传给Detail组件。
						// props:true

						//props的第三种写法,值为函数
						props($route){//函数会收到一个参数$route
							return {// 返回值必须是对象,数据放在对象
								//所有键值对都会以props的形式传给Detail组件
								id:$route.query.id,//必须是键值对
								title:$route.query.title,
								a:1,
								b:'hello'
							}
						}
					}
				]
			}
		]
	}
]

然后在接收数据的组件中配置props来接收数据:

<template>
	<ul>
    <!-- router/index.js中配置的props是一个对象:
    <li>a:{{a}}</li>
    <li>b:{{b}}</li>
    -->
    <!-- router/index.js中配置的props是一个布尔值: -->
		<li>消息编号:{{id}}</li>
		<li>消息标题:{{title}}</li>
    <!--router/index.js中配置的props是一个函数
    <li>a:{{a}}</li>
    <li>b:{{b}}</li>
    <li>消息编号:{{id}}</li>
	<li>消息标题:{{title}}</li>
    -->
	</ul>
</template>

<script>
	export default {
		name:'Detail',
    	//props:['a','b'],//router/index.js中配置的props是一个对象
		props:['id','title'],//router/index.js中配置的props是一个布尔值
    	//props:['id','title','a','b'],//router/index.js中配置的props是一个函数
	}
</script>

跳转路由时:

<!-- 跳转路由并携带params参数,to的字符串写法 -->
<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->

<!-- 跳转路由并携带params参数,to的对象写法 -->
<router-link :to="{
	name:'xiangqing',
	query:{
		id:m.id,
		title:m.title
	}
}">
	{{m.title}}
</router-link>

router-link标签的replace属性

一、作用:控制路由跳转时操作浏览器历史记录的模式
二、路由对浏览器历史记录的影响:浏览器中有两个常用的按钮:后退、前进,这两个按钮都是依赖于浏览器的历史记录来工作。使用<router-link>标签跳转链接,每次点击都会形成历史记录,且默认的操作模式是push,不破坏任何一条记录,不断往里面压入记录。对历史记录的操作除了push还有replace,replace最大的作用就是替换掉栈顶那一条。也就是说,浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
三、如何开启replace模式,给<router-link>标签添加replace属性,即可开启replace模式:<router-link replace .......>News</router-link>

编程式路由导航

一、有时候我们不能使用<router-link>标签实现跳转:

  1. 如果导航项得使用button写,点击按钮实现跳转,那就不能用<router-link>标签了,因为<router-link>标签最终转成了a标签,而我们要的是button
  2. 有时我们需要等一会再跳转,比如一进入页面,等一会儿跳转到xx页面,之前我们需要引导用户点击再跳转,现在我们没条件让用户点击,我们就想实现跳转

二、编程式路由导航:不借助<router-link>实现路由跳转的路由导航。让路由跳转更加灵活
三、具体编码:

//$router的两个API
//不要写route,route只是个规则,别人去运用这个规则,router才有指挥权
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() //可前进也可后退
//this.$router.go(n):n>0标签前进n步,n<0表示后退|n|步

缓存路由组件

一、作用:让不展示的路由组件保持挂载,不被销毁。
二、具体编码:

  1. 缓存多个路由组件:<keep-alive :include="['News','Message']">
  2. 缓存一个路由组件,使用include指定哪些组件需要缓存,include的值是组件名:
<keep-alive include="News">
	<router-view></router-view>
</keep-alive>
  1. 像下面这样写,以后所有出现在中展示的内容,都会被缓存
<keep-alive>
	<router-view></router-view>
</keep-alive>

三、路由组件有两个独有的钩子,用于捕获路由组件的激活状态:

activated路由组件被激活时触发。
deactivated路由组件失活时触发。

路由守卫

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

全局守卫

一、路由暴露前为其添加全局路由守卫
二、全局前置路由守卫:初始化的时候被调用、每次路由切换之前被调用

  1. 使用router.beforeEach(函数)指定一个函数,在每次初始化时、每次路由切换之前都会调用这个函数。
  2. to会接收到目标路由的信息,from会接收到源路由的信息,next函数用于控制是否进行下一步,调用才会进行,不调用的话,路由切换会卡在router.beforeEach(函数)中不进行下一步。
router.beforeEach((to,from,next)=>{
	console.log('前置路由守卫',to,from)
	// if (to.path === '/home/news' || to.path === '/home/message'){//也可以使用这种方法判断是否需要鉴权
	// if (to.name === 'xinwen' || to.name === 'xiaoxi'){//也可以使用这种方法判断是否需要鉴权
  1. 如果我要对几十个路由进行判断,上面的写法太麻烦了,给每个路由配置都加上meta属性,用于标识本路由是否需要校验权限,meta被称为路由元信息,可以在里面写程序员自己配置的东西,那么我们可以在需要鉴权的配置对象上中的meta中添加个属性,这里添加的是isAuth
routes:[
	{//这是个配置对象,配置对象最大的特点就是里面的每一项都是按照设计好的来的,你不能在这里面添加稀奇古怪的属性
		name:'guanyu',
		path:'/about',
		component:About,
		meta:{isAuth:true,title:'关于'}
	}
]
  1. 那么在这里只用判断to.meta.isAuth是否为true,为true则鉴权
if(to.meta.isAuth){ //判断是否需要鉴权
	if(localStorage.getItem('school')==='atguigu'){
		next()
	}else{
		alert('学校名不对,无权限查看!')
	}
}else{
	next()
}

三、全局后置路由守卫:初始化的时候被调用、每次路由切换之后被调用

  1. 使用router.afterEach(函数)指定一个函数,在每次初始化时、每次路由切换之后都会调用这个函数
  2. to会接收到目标路由的信息,from会接收到源路由的信息,
  3. 有这么一个场景:你一进入一个网页,页签展示尚硅谷,然后你点击了HOME,页签展示主页,你点击About,页签展示关于。在这种情况下,就需要router.afterEach(函数)
router.afterEach((to,from)=>{
	console.log('后置路由守卫',to,from)
	document.title = to.meta.title || '硅谷系统'
})

独享守卫

一、独享路由守卫:某一个路由所单独享用的路由守卫,只有前置路由守卫。
二、

routes:[
	{
		name:'zhuye',
		path:'/home',
		component:Home,
		meta:{title:'主页'},
		children:[
			{
				name:'xinwen',
				path:'news',
				component:News,
				meta:{isAuth:true,title:'新闻'},
				/*
				进入News组件前会调用beforeEnter指定的函数,该函数同样会收到to、from、next三个参数,
				用法同全局路由守卫
				*/
				beforeEnter: (to, from, next) => {
					console.log('独享路由守卫',to,from)
					if(to.meta.isAuth){ //判断是否需要鉴权
						if(localStorage.getItem('school')==='atguigu'){
							next()
						}else{
							alert('学校名不对,无权限查看!')
						}
					}else{
						next()
					}
				}
			}
		]
	}
]

组件内路由守卫

组件内路由守卫:在组件里写路由守卫。当你想给某组件单独写一些逻辑,可以在组件内路由守卫中实现

  1. 通过路由规则,进入该组件时被调用

通过路由规则,进入组件的过程:点击后路径变成/about,前端路由器检测到路径的变化,匹配规则后进入组件展示
不通过路由规则进入组件:比如我一打开页面,xx组件就展示了(通过在页面中写<xx />展示组件),xx组件就不是通过路由规则进入组件的

beforeRouteEnter (to, from, next) {
	console.log('About--beforeRouteEnter',to,from)
	if(to.meta.isAuth){ //判断是否需要鉴权
		if(localStorage.getItem('school')==='atguigu'){
			next()
		}else{
			alert('学校名不对,无权限查看!')
		}
	}else{
		next()
	}
}
  1. 通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
	console.log('About--beforeRouteLeave',to,from)
	next()
}

路由器的两种工作模式

一、对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
二、hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
三、hash模式:(默认)

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

四、history模式:

  1. 地址干净,美观 。
  2. 兼容性和hash模式相比略差。
  3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。 在node中使用connect-history-api-fallback库解决。
    (1)在服务器终端安装:npm i connect-history-api-fallback
    (2)在后端引入:const history = require(connect-history-api-fallback)
    (3)再在静态资源使用之前使用connect-history-api-fallback:
const app = express() app.use(history) 
app.use(express.static(__dirname+'/static'))//使用静态资源

五、路由模式默认为hash模式,可以在创建路由器时,通过指定mode配置项将路由模式改为history模式:new VueRouter({mode:'history'})

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值