vue -【nextTick】-【过度与动画】-【插槽】-【配置代理服务器】-【vuex】-【路由】
目录
- nextTick
- 过度与动画
- vue脚手架配置代理服务器
- 插槽
- 默认插槽
- 具名插槽
- 作用域插槽
- vuex
- 搭建vuex环境
- 基本使用:
- getters的使用
- 四个map方法的使用
- mapState方法
- mapGetters方法
- mapMutations方法
- mapActions方法
- 模块化+命名空间
- 路由
- 基本使用
- 多级路由/嵌套路由
- 路由的query参数
- 命名路由
- 路由的params参数
- 路由的props配置
- router-link标签的replace属性
- 编程式路由导航
- 缓存路由组件
- 路由守卫
- 全局守卫
- 独享守卫
- 组件内路由守卫
- 路由器的两种工作模式
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属性,有的话置truetodo.isEdit = true}else{// 没有的话,添加并置truethis.$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)v-enter
:进入的起点
(2)v-enter-active
:进入过程中
(3)v-enter-to
:进入的终点
元素离开的样式:
(1)v-leave
:离开的起点
(2)v-leave-active
:离开过程中
(3)v-leave-to
:离开的终点 - 使用
<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属性,不然两个元素的动画就一样了
- 备注:若有多个元素需要过度,则需要使用:
<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
- 在项目终端安装:
npm install animate.css
- 引入:
import 'animate.css'//因为引入的是样式,不是JS模块,所以直接在组件中写:import 路径
- 给
<transition>
或<transition-group>
标签添加:name="animate__animated animate__bounce"
- 给
<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 appearname="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"
}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)
二、方法二:编写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': ''}}}}
}
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
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发送网络请求
- 在项目终端安装axios:
npm i axios
- 在使用的组件中引入:
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>
父组件(使用者)中:
- 方法一:通过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>
- 方法二:在
<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组件中,你要用数据你又拿不到数据。那么可以:
- 在Category组件中定义插槽,在插槽中把数据传过去
子组件中:
<template><div><!--数据在组件中,根据数据所生成的结构由组件的使用者决定下面一行代码会把games和msg传给插槽的使用者--><slot :games="games" msg="hello">我是默认的一些内容</slot></div>
</template><script>export default {name:'Category',props:['title'],//数据在子组件自身data() {return {games:['红色警戒','穿越火线','劲舞团','超级玛丽']}},}
</script>
- 使用插槽的组件将要放在插槽中的内容用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({//创建vmel:'#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>
⚠️几个注意点
- 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。 - 整个应用只有一个router,可以通过组件的
$router
属性获取到。
多级路由/嵌套路由
一、配置路由规则,使用children配置项:
routes:[{path:'/about',component:About},{path:'/home',// 一级路由要加/component:Home,children:[// 配置Home路由下的子路由,也是个数组,因为Home路由下可能有很多个子路由{path:'news',// 某个一个路由的子路由不加/,因为人家底层在遍历规则时,已经给你加上/了,component:News,},{path:'message',//此处一定不要写:/messagecomponent:Message,}]}
]
跳转时,如果路由链接是二级路由或子级路由,必须带着父亲的路径,也就是写完整路径:<router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
路由的query参数
一、传递参数
- 跳转路由并携带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>
- 跳转路由并携带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>
命名路由
一、作用:可以简化路由的跳转
二、如何使用:
- 给路由命名:
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,}]}]}
]
- 简化跳转
简化前,需要写完整的路径:<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}]} ]
}
二、传递参数
- 跳转并携带params参数,to的字符串写法:
<router-link :to="/home/message/detail/666/你好">跳转</router-link>
不用写?
,直接把参数拼进去,那这样路由可能就会把参数当成路径,所以我们在配置路由时,要在路径后面使用:
指定占位符 - 跳转并携带params参数,to的对象写法,此时只能用name指定路由,不能用path:
<router-link :to="{name:'xiangqing',// 如果携带的是params参数,这里只能用name指定路由,不能用pathparams:{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){//函数会收到一个参数$routereturn {// 返回值必须是对象,数据放在对象//所有键值对都会以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> --><!-- 跳转路由并携带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>
标签实现跳转:
- 如果导航项得使用button写,点击按钮实现跳转,那就不能用
<router-link>
标签了,因为<router-link>
标签最终转成了a标签,而我们要的是button - 有时我们需要等一会再跳转,比如一进入页面,等一会儿跳转到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|步
缓存路由组件
一、作用:让不展示的路由组件保持挂载,不被销毁。
二、具体编码:
- 缓存多个路由组件:
<keep-alive :include="['News','Message']">
- 缓存一个路由组件,使用include指定哪些组件需要缓存,include的值是组件名:
<keep-alive include="News"><router-view></router-view>
</keep-alive>
- 像下面这样写,以后所有出现在中展示的内容,都会被缓存
<keep-alive><router-view></router-view>
</keep-alive>
三、路由组件有两个独有的钩子,用于捕获路由组件的激活状态:
activated路由组件被激活时触发。
deactivated路由组件失活时触发。
路由守卫
一、作用:对路由进行权限控制
二、分类:全局守卫、独享守卫、组件内守卫
全局守卫
一、路由暴露前为其添加全局路由守卫
二、全局前置路由守卫:初始化的时候被调用、每次路由切换之前被调用
- 使用
router.beforeEach(函数)
指定一个函数,在每次初始化时、每次路由切换之前都会调用这个函数。 - 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'){//也可以使用这种方法判断是否需要鉴权
- 如果我要对几十个路由进行判断,上面的写法太麻烦了,给每个路由配置都加上meta属性,用于标识本路由是否需要校验权限,meta被称为路由元信息,可以在里面写程序员自己配置的东西,那么我们可以在需要鉴权的配置对象上中的meta中添加个属性,这里添加的是isAuth
routes:[{//这是个配置对象,配置对象最大的特点就是里面的每一项都是按照设计好的来的,你不能在这里面添加稀奇古怪的属性name:'guanyu',path:'/about',component:About,meta:{isAuth:true,title:'关于'}}
]
- 那么在这里只用判断
to.meta.isAuth
是否为true,为true则鉴权
if(to.meta.isAuth){ //判断是否需要鉴权if(localStorage.getItem('school')==='atguigu'){next()}else{alert('学校名不对,无权限查看!')}
}else{next()
}
三、全局后置路由守卫:初始化的时候被调用、每次路由切换之后被调用
- 使用
router.afterEach(函数)
指定一个函数,在每次初始化时、每次路由切换之后都会调用这个函数 - to会接收到目标路由的信息,from会接收到源路由的信息,
- 有这么一个场景:你一进入一个网页,页签展示尚硅谷,然后你点击了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()}}}]}
]
组件内路由守卫
组件内路由守卫:在组件里写路由守卫。当你想给某组件单独写一些逻辑,可以在组件内路由守卫中实现
- 通过路由规则,进入该组件时被调用
通过路由规则,进入组件的过程:点击后路径变成/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()}
}
- 通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {console.log('About--beforeRouteLeave',to,from)next()
}
路由器的两种工作模式
一、对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
二、hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
三、hash模式:(默认)
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
四、history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端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'})
vue -【nextTick】-【过度与动画】-【插槽】-【配置代理服务器】-【vuex】-【路由】相关推荐
- Vue | 使用Vue脚手架 【脚手架的基本使用+ref属性+props属性+mixin混入+插件scoped样式+TodoList+浏览器本地存储+组件的自定义事件+全局事件总线+过度与动画】
文章目录 脚手架的基本使用 初始化脚手架 分析脚手架结构 render函数 修改默认配置 ref属性 props属性 mixin混入 插件 scoped样式 Todo-list案例 组件化编码流程(通 ...
- vue动画效果配置和弹层css sticky footer
vue动画效果配置 有两种方式: 一种是css方式 需要注意 4 个(CSS)类名在 enter/leave 的过渡中切换: v-enter: 定义进入过渡的开始状态.在元素被插入时生效,在下一个帧移 ...
- Vue全家桶(一):Vue基础+Vue-Cli+Vue组件化+过渡动画
目录 1.Vue概述 1.1 认识Vue 1.2 Vue的两核心 1.3 Vue的初体验 1.4 Vue的生命周期 2. Vue-CLI (Command Line Interface) 3. Vue ...
- VUE学习(二十)、插槽
VUE学习(二十).插槽 一.默认插槽 1.Category.vue <template><div class="category"><h3>{ ...
- 7 种最棒的 Vue Loading 加载动画组件测评与推荐 - 穷尽市面上所有加载动画效果(Vue loader)类型
本文完整版:<7 种最棒的 Vue Loading 加载动画组件测评与推荐> 目录 7 种不同类型的 Vue Loading 加载动画组件 1. Vue Simple Spinner - ...
- 浏览器事件循环机制与Vue nextTick的实现
浏览器事件循环机制 先上一段简单的代码 console.log('aa'); setTimeout(() => { console.log('bb')}, 0); Promise.resolve ...
- vue2知识点:vue-cli脚手架配置代理服务器
文章目录 6.3vue-cli脚手架配置代理服务器 6.3.1解决跨域问题:配置代理_方式1 案例:开2台模拟服务器,模拟客户端端口8080调用2台服务器端口叫5000和5001,实现ajax解决跨域 ...
- vue实现双开门动画
需求: 前一个页面离开时有双开门动画,后一个页面有缩放动画的效果. 不太理解双开门动画的小伙伴,可以参考ppt的门动画 版本迭代 第一次迭代 设计 第一想法就是通过创建一个父元素,并为其添加两个子元素 ...
- vue脚手架解决跨域问题-------配置反向代理
vue脚手架解决跨域问题-------配置反向代理 参考文章: (1)vue脚手架解决跨域问题-------配置反向代理 (2)https://www.cnblogs.com/zbx-boke/p/9 ...
- Vue.nextTick()理解
什么是Vue.nextTick() 官方解释:在下次 DOM 更新循环结束之后执行延迟回调.在修改数据之后立即使用这个方法,获取更新后的 DOM. 注意:重点是获取更新后的DOM 就是在开发过程中有个 ...
最新文章
- 【嵌入式开发】C语言 命令行参数 函数指针 gdb调试
- leetcode - two-sum
- Python中的测试工具
- Facebook:15年来最重要的转型,F8大会掀开打造私密社交网络的新篇章
- SAP CRM WebClient UI上文本是否可编辑,取决于哪些条件
- 猜1-10的数字python脚本
- tinycore php,tinycore中文支持
- LazyInitializationException的四种解决方案–第1部分
- 12c oracle 激活_Oracle 12C 安装教程
- 薇娅夫妇合伙企业正式注销 系决议解散
- vsphere6.0实验拓扑-虚拟机版
- 贝叶斯(五)贝叶斯决策
- 小米蓝牙耳机使用说明书
- Excel-事件(Workbook、Worksheet、Range、OnKey/OnTime)
- freemarker字符串替换操作
- Versal ACAP AI 引擎入门
- 远程同步软件rsync(一)
- 友盟推送注册成功但是收不到推送
- 2014中国飞思卡尔技术论坛即将开幕
- linux 安装wkhtmltopdf 所出的问题
热门文章
- 古文观止卷七_陈情表_李密
- 国产操作系统Deepin的安装
- 强大的合成器微信小程序源码支持视频,gif动态证件照等等几十种功能
- 使用Uber-go Zap日志库
- 倪光南:网络不安全要挨打 不用县县都建数据中心
- mysql column specified twice_Column 'box_id' specified twice 错误
- linux程序设计学习心得,几点学习Linux编程的建议
- 可乐要加冰才好喝啊---装饰模式
- 仿生蛇类机器人 特点_仿生蛇机器人
- 小马激活工具拒绝访问cannot open file c:\oemsf解决方法