Vue2进阶笔记
- 一、基础知识
- 1.1 computed计算属性
- 1.2 watch监视属性
- 1.3 动态绑定样式
- 1.4 列表循环渲染 key的探讨
- 1.5 列表过滤
- 1.6 数据监视
- 1.7 表单收集
- 1.8 过滤器
- 1.9 生命周期函数
- 1.10 nextTick
- 1.11 动画与过渡
- 1.12 脚手架配置跨域代理
- 二、组件化开发
- 2.1 演替与定义
- 2.2 使用与注册
- 2.3 VueComponent
- 2.4 vue-cli脚手架
- 2.5 main.js中的render
- 2.6 ref属性
- 2.7 props配置
- 2.8 mixin混入
- 2.9 插件
- 2.10 scoped样式
- 三、组件通信
- 3.1 父传子
- 3.2 子传父
- 3.3 全局事件总线
- 3.4 消息订阅与发布(pubsub)
- 3.5 插槽
- 四、Vuex
- 4.1 概念
- 4.2 环境搭建
- 4.3 基本使用
- 4.4 getters的使用
- 4.5 四个map方法
- 4.6 模块化
- 五、vue-router
- 5.1 基本使用
- 5.2 多级路由
- 5.3 路由query参数
- 5.4 路由params传参
- 5.5 路由命名
- 5.6 路由props配置
- 5.7 链接跳转replace属性
- 5.8 编程式路由导航
- 5.9 缓存路由组件
- 5.10 路由组件生命周期函数
- 5.11 路由守卫
- 5.12 hash与history工作模式
- 5.13 meta元数据
Vue2进阶笔记,受益于尚硅谷天禹老师课程,在此表示感谢,全文共三万余字。
一、基础知识
tips:
1、所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
2、所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数等),最好写成箭头函数这样this的指向才是vm或组件实例对象。
1.1 computed计算属性
在computed中,可以定义一些属性,即计算属性。计算属性具有缓存功能,相比较methods效率更高。
计算属性本质是方法,只是在使用这些计算属性的时候,把他们的名称直接当作属性来使用,并不会把计算属性当作方法去调用,不需要加小括号()调用。
计算属性的求值结果会被缓存起来,方便下次直接使用(多次调用只要内部数据不改变就不会重新求值,改变了也只会计算一次,虽然有多个地方引用此属性)。getter方法内部无论如何都要return出去一个值。
<body>
<div id="app">
<h1>计算属性:computed的getter/setter</h1> <br/>
姓:<input type="text" v-model="firstName"> <br/>
名:<input type="text" v-model="lastName"> <br/>
全名:<input type="text" v-model="fullName">>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
firstName:"zhang",
lastName:"san",
},
computed: {
/* 完全写法,可读可修改
fullName:{
get:function(){
return this.firstName + "-" + this.lastName
},
set:function(value){
var list = value.split(' ');
this.firstName=list[0]
this.lastName=list[1]
}
}
*/
// 只读不修改,只有getter没有setter,简写
fullName(){
return this.firstName + "-" + this.lastName
}
},
});
</script>
</body>
1.2 watch监视属性
Watch概述
一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。
深度监视:
(1). Vue中的watch默认不监测对象内部值的改变(一层)。
(2). 配置deep:true可以监测对象内部值改变(多层)。
备注:
(1). Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
(2). 使用watch时根据数据的具体结构,决定是否采用深度监视。
简单的监听
<body>
<div id="app">
<input type="text" v-model="num">
</div>
<script src="vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
num: ''
},
// 创建vue实例时就知道需要监视那个数据项,用这种方法
watch: {
// 不考虑immediate和deep只有hander一个配置项时,可以简写
num(newVal, oldVal) {
// 监听 num 属性的数据变化
// 作用 : 只要 num 的值发生变化,这个方法就会被调用
// 第一个参数 : 新值
// 第二个参数 : 旧值,之前的值
console.log('oldVal:',oldVal)
console.log('newVal:',newVal)
}
},
// 正常写法 根据用户行为,方才知道那个数据项需要监视,用这种方法
vm.$watch('num',{
// 每个属性值发生变化就会调用这个函数
handler(newVal, oldVal) {
console.log('oldVal:', oldVal)
console.log('newVal:', newVal)
},
// 立即处理 进入页面就触发
immediate: true,
// 深度监听 监视多级结构中所有属性的变化
deep: true
}
})
// 不考虑immediate和deep只有hander一个配置项时,可以简写
vm.$watch('num',function(newVal, oldVal) {
console.log('oldVal:', oldVal)
console.log('newVal:', newVal)
})
</script>
</body>
immediate(立即处理 进入页面就触发)
deep(深度监听)
对象和数组都是引用类型,引用类型变量存的是地址,地址没有变,所以不会触发watch。这时我们需要进行深度监听,就需要加上一个属性 deep,值为 true。
<body>
<div id="app">
<input type="button" value="更改名字" @click="change">
</div>
<script src="vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
food: {
id: 1,
name: '冰激凌'
}
},
methods: {
change() {
this.food.name = '棒棒糖'
}
},
watch: {
// 完整写法
// 第一种方式:监听整个对象,每个属性值的变化都会执行handler
// 注意:属性值发生变化后,handler执行后获取的 newVal 值和 oldVal 值是一样的
food: {
// 每个属性值发生变化就会调用这个函数
handler(newVal, oldVal) {
console.log('oldVal:', oldVal)
console.log('newVal:', newVal)
},
// 立即处理 进入页面就触发
immediate: true,
// 深度监听 监视多级结构中所有属性的变化
deep: true
},
// 第二种方式:监听对象的某个属性,被监听的属性值发生变化就会执行函数
// 函数执行后,获取的 newVal 值和 oldVal 值不一样
'food.name'(newVal, oldVal) {
console.log('oldVal:', oldVal) // 冰激凌
console.log('newVal:', newVal) // 棒棒糖
}
}
})
</script>
</body>
Watch和computed的区别
1、 computed支持缓存,只有依赖数据发生改变,才会重新进行计算;而watch不支持缓存,数据变,直接会触发相应的操作。
2、computed不支持异步,当computed内有异步操作时无效,无法监听数据的变化,而watch支持异步。
3、computed属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值;而watch监听的函数接收两个参数,第一个参数是最新的值,第二个参数是输入之前的值。
4、如果一个属性是由其它属性计算而来的,这个属性依赖其它属性,多对一或者一对一,一般用computed;而当一个属性发生变化时,需要执行对应的操作,一对多,一般用watch。
1.3 动态绑定样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>绑定样式</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
<style>
.basic{
width: 400px;
height: 200px;
border: 2px solid cornflowerblue;
}
.normal{
background-color: lightsteelblue;
}
.happy{
background-color: cornflowerblue;
}
.sad{
background-color: chartreuse;
}
.test1{
font-size: 20px;
text-align: center;
}
.test2{
border-radius: 10px;
}
.test3{
font-style: inherit;
background-color: red;
}
</style>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root" >
<!-- 绑定class样式,字符串写法。适用于:样式的类名不确定,需要动态绑定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}></div><br><br>
<!-- 绑定class样式,数组写法。适用于:要绑定的个数不确定,名字也不确定 -->
<div class="basic" :class="arr" >{{name}}></div><br><br>
<!-- 绑定class样式,对象写法。适用于:要绑定的个数不确定,名字也不确定 -->
<div class="basic" :class="classObj" >{{name}}></div><br><br>
<!-- 绑定style样式,对象写法。 -->
<div class="basic" :style="styleObj" >{{name}}></div><br><br>
<!-- 绑定style样式,数组写法,使用较少。 -->
<div class="basic" :style="styleArr" >{{name}}></div><br><br>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
new Vue({
el:"#root",
data:{
name:"才疏学浅",
mood:'normal',
arr:['test1','test2','test3'],
classObj:{
test1:false,
test2:false,
test3:false
},
styleObj:{
// 这里的属性名要遵循驼峰命名规范 font-size --> fontSize
fontSize: '40px',
color: 'blue'
},
styleArr:[
{
fontSize: '40px',
color: 'blue'
},
{
backgroundColor: 'red'
}
]
},
methods: {
changeMood(){
const arr = ['normal','happy','sad']
this.mood = arr[Math.floor(Math.random()*3)]
}
},
})
</script>
</html>
1.4 列表循环渲染 key的探讨
当数组数据有可能顺序被破坏时,采用数组index作为key值可能导致:输入框内容错乱、效率过低的情况。当不写key时,vue会自动将数组的index索引值作为key值进行deff虚拟DOM对比算法。
采用数组自带id属性,则不会出现这种情况。
面试题:react、vue中的key有什么作用?(key的内部原理)
1、虚拟DOM中key的作用:
key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DON】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
2、对比规则:
(1). 旧虚拟DOM中找到了与新虚拟DOM相同的key:
若虚拟DOM中内容没变,直接使用之前的真实DOM。
若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
(2). 旧虚拟DOM中未找到与新虚拟DOM相同的key:
创建新的真实DOM,随后渲染到到页面。
3、用index作为key可能会引发的问题:
若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 – > 界面效果没问题,但效率低。
如果结构中还包含输入类的DOM:会产生错误DOM更新 – > 界面有问题。
4、开发中如何选择key? :
最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
1.5 列表过滤
监视(侦听)属性实现列表过滤:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!--引入Vue-->
<script type="text/javascript" src="../js/vue.js"></script>
<title></title>
</head>
<body>
<!--准备好一个容器-->
<div id="root">
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="p in filPersons" :key="p.id">
{{p.name}}-{{p.age}}--{{p.sex}}
</li>
</ul>
</div>
<script type="text/javascript">
Vue.config.productionTip = false;
new Vue({
el: '#root',
data: {
keyWord: '',
persons: [
{ id: '001', name: '马冬梅', age: 19, sex: '女' },
{ id: '002', name: '周冬雨', age: 20, sex: '女' },
{ id: '003', name: '周杰伦', age: 21, sex: '男' },
{ id: '003', name: '温兆伦', age: 22, sex: '男' }
],
filPersons: []
},
watch: {
keyWord: {
immediate: true,
handler(val) {
this.filPersons = this.persons.filter((p) => {
return p.name.indexOf(val) !== -1
})
}
}
}
})
</script>
</body>
</html>
用计算属性实现列表过滤:
computed:{
filPersons(){
return this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord)!==-1
})
}
}
用计算属性实现过滤,同时需要列表排序:
computed:{
filPersons(){
const arr= this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord)!==-1
})
//判断一下是否需要排序
if(this.sortType){
arr.sort((a,b)=>{
return this.sortType===1?b.age-a.age:a.age-b.age
})
}
return arr
}
}
1.6 数据监视
Vue会监视data中所有层次的数据。通过setter实现监视,且在new Vue时就传入要监测的数据。
对象中后追加的属性,Vue默认不做响应式处理。如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,prpertyName/index,value)
vm.$set(target,prpertyName/index,value)
通过包裹数组更新元素的方式实现监测数组中的数据,本质是:1. 调用原生对应的方法对数组进行更新。2. 重新解析模板,进而更新页面。
在Vue修改数组中的某个元素时一定要使用如下方法:
1、使用API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
2、使用 Vue.set或者vm.$set(this.$set)
注意:Vue.set和vm.$set不能给vm或vm的根数据对象添加属性。
1.7 表单收集
v-model默认收集的是表单的value值,这里有几个需要主要的点。
若<input type="text"/>,则v-model收集的是value值,用户输入的就是value值。
若<input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
若<input type="checkbox"/>
1、没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,布尔值)
2、配置了input的value属性
v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,布尔值)
v-model的初始值是数组,那么收集的就是value组成的数组。
备注:v-model的三个修饰符:
1、lazy:失去焦点后再收集数据。
2、number:输入字符串转为有效的数字。
3、trim:输入首尾空格过滤。
1.8 过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
// 语法:
注册过滤器:Vue.filter(name,callback)或new Vue{filters:{}}
使用过滤器:{{xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
备注:
过滤器也可以接收额外参数,多个过滤器能够串联。
并没有改变原本的数据,是产生新的对应的数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>过滤器</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="../js/dayjs.min.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h3>(计算属性实现)现在是{{fmtTime}}</h3>
<!-- methods实现 -->
<h3>(methods实现)现在是{{getfmtTime()}}</h3>
<!-- 过滤器实现 -->
<h3>(过滤器实现)现在是{{time | timeFormater}}</h3>
<!-- 过滤器传参 -->
<h3>(过滤器传参)现在的日期是{{time | timeFormater('YYYY年_MM月_DD日')}}</h3>
<!-- 多个过滤器传参 -->
<h3>(多个过滤器传参)今年是{{time | timeFormater('YYYY年_MM月_DD日') | mySlice}}</h3>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false // 阻止 vue 在启动时生成生产提示
// 全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,5)
})
new Vue({
el:'#root',
data:{
name:'才疏学浅的小缘同学',
time: 1647417712099
},
computed:{
fmtTime(){
return dayjs(this.time).format('YYYY年-MM月-DD日 HH:mm:ss')
}
},
methods: {
getfmtTime(){
return dayjs(this.time).format('YYYY年-MM月-DD日 HH:mm:ss')
}
},
// 局部过滤器
filters:{
timeFormater(value,str='YYYY年-MM月-DD日 HH:mm:ss'){
return dayjs(value).format(str)
},
}
})
</script>
</html>
1.9 生命周期函数
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期的函数,这给了用户在不同阶段添加自己的代码的机会。
- beforeCreate(创建前)
- created (创建后)
- beforeMount (载入前)
- mounted (载入后)
- beforeUpdate (更新前)
- updated (更新后)
- beforeDestroy( 销毁前)
- destroyed (销毁后)
Vue生命周期函数就是vue实例在某一个时间点会自动执行的函数
当Vue对象创建之前触发的函数(beforeCreate)
Vue对象创建完成触发的函数(Created)
当Vue对象开始挂载数据的时候触发的函数(beforeMount)
当Vue对象挂载数据的完成的时候触发的函数(Mounted)
当Vue对象中的data数据发生改变之前触发的函数 (beforeUpdate)
当Vue对象中的data数据发生改变完成触发的函数(Updated)
当Vue对象销毁之前触发的函数 (beforeDestroy)
当Vue对象销毁完成触发的函数(Destroy)
1.10 nextTick
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次DOM更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新的DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
1.11 动画与过渡
指定过渡动画的步骤:
- 在目标元素外面包裹<transition name=“xxx”>
- 为name名指定CSS样式
指定过渡样式:transition(css3的属性)
指定隐藏时的样式:opacity/或者其他(width等等) - 如果有多个元素需要过渡,则需要使用:
<<transition-group>
,并且每个元素都要指定key值。
<transition name="xxx">
<h1>你好</h1>
</transition>
- 其他的第三方库/animate.style,官网有文档,自行学习。
过渡的相关类名:
xxx-enter-active:指定显示的 transition
xxx-leave-active:指定隐藏的 transition
xxx-enter/xxx-leave-to:指定隐藏时的样式
<style>
/*入场动画规则*/
@keyframes boxenter {
0% {
transform: translateX(-100px);
}
100% {
transform: translateX(0);
}
}
/*出场动画规则*/
@keyframes boxleave {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-100px);
}
}
/*这里应用入场动画规则*/
.v-enter-active {
animation: boxenter 3s;
}
/*这里应用出场场动画规则*/
.v-leave-active {
animation: boxleave 3s;
}
</style>
<body>
<div id="app">
<button @click="change">切换</button>
<!-- 使用transition标签包装需要动画的元素 -->
<transition>
<mycomponent v-if="isShow"></mycomponent>
</transition>
</div>
...
</body>
<style>
/* demo1 */
/* 显示/隐藏的过渡效果 */
.xxx-enter-active,.xxx-leave-active{
transition: opacity 1s;
}
/* 隐藏时的样式 */
.xxx-enter,.xxx-leave-to{
opacity: 0;
}
/* demo2 */
/* 显示的过渡效果 */
.yyy-enter-active{
transition: all 1s;
/* transform: translateX(20px); */
}
/* 隐藏时的过渡效果 */
.yyy-leave-active{
transition: all 3s;
}
/* 隐藏时的样式 */
.yyy-enter,.yyy-leave-active{
opacity: 0;
transform: translateX(20px);
}
</style>
<body>
<div id="demo1">
<button @click="isShow = !isShow">Toggle</button>
<transition name="xxx">
<p v-show="isShow">hello</p>
</transition>
</div>
<div id="demo2">
<button @click="isShow = !isShow">Toggle</button>
<transition name="yyy">
<p v-show="isShow">hello</p>
</transition>
</div>
<script src="../js/vue.js" type="text/javascript" charset="utf-8"></script>
<script>
new Vue({
el:"#demo1",
data:{
isShow:true
}
})
new Vue({
el:"#demo2",
data:{
isShow:true
}
})
</script>
</body>
1.12 脚手架配置跨域代理
方法一:
在vue.config.js
中添加如下配置:
devServer:{
proxy:'http://localhost:服务器端口'
}
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可。
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源(public目录下资源)时,那么该请求会转发给服务器(优先匹配前端资源)
方法二:
编写vue.config.js
配置具体代理规则:
module.exports = {
devServer: {
proxy: {
"/api": { // 匹配所有以'/api'开头的请求路径
target: "http://localhost:5000", // 代理目标的基础路径
changeOrigin: true, // 用于控制请求头中的host值
pathRewrite: { "^/api": "" },
},
"/other": { // 可以配置多个代理路径
target: "http://localhost:5001",
changeOrigin: true,
pathRewrite: { "^/other": "" },
},
},
},
};
/**
* changeOrigin设置为true时,服务器收到的请求头中的host为: localhost:5000
* changeOrigin设置为false时,服务器收到的请求头中的host为: localhost:8080
* changeorigin默认值为true
*/
说明:
- 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
- 缺点:配置略微繁琐,请求资源时必须加前缀。
二、组件化开发
2.1 演替与定义
组件的定义:实现应用中局部功能代码和资源的集合。
2.2 使用与注册
组件化编码流程:
- 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
- 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
(1)一个组件在用:放在组件自身即可。
(2)一些组件在用:放在他们共同的父组件上(状态提升)。- 实现交互:从绑定事件开始。
组件的使用分为:非单文件组件、单文件组件。区别是否将单个html文件解耦为多个vue文件。
标准化开发中,我们会创建一个 app 组件去管理(领导)下属的所有组件,最顶级的vm管理app。(一人之下万人之上)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>几个注意点</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
几个注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
备注:
(1).组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
(2).可以使用name配置项指定组件在开发者工具中呈现的名字。
2.关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不用使用脚手架时,<school/>会导致后续组件不能渲染。
3.一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options
-->
<!-- 准备好一个容器-->
<div id="root">
<h1>{{msg}}</h1>
<school></school>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
//定义组件
const s = Vue.extend({
name:'atguigu',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
}
})
new Vue({
el:'#root',
data:{
msg:'欢迎学习Vue!'
},
components:{
school:s
}
})
</script>
</html>
2.3 VueComponent
组件本质其实是一个名为VueComponent
的构造函数,且不是由程序员来定义的,是由Vue.extend
生成的。
- 当我们写出组件名作为标签时,Vue解析会自动帮我们创建该组件名的对象实例并且执行
new VueComponent(options)
。 - 每次调用
Vue.extend
返回的都是一个新的VueComponent
学过后端的应该很清楚。 - 在组件中的this所指向的是
VueComponent
类似于java的动态绑定。
组件实例对象(vc)可以访问到Vue原型上的属性、方法。
2.4 vue-cli脚手架
脚手架文件结构
更正:App.vue 文件名默认是可以更改的,只不过不推荐。
脚手架文件目录
// 当引入第三方公共css库,使用import导入存在报错(import会严格检查),可将css库放在public目录下,在index.html主页面通过link引入。
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件
报错信息:error Component name “School” should always be multi-word vue/multi-word-component-names
报错原因:自己在给组件命名时没有使用推荐的大驼峰或者’-'拼接单词,所以编译的时候报错,实际上是语法检测的问题
vue.config.js
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
// 关闭语法检测
lintOnSave: false,
});
为什么要写那么多版本的vue包,因为模板解析器占体积太大了,大概占整个Vue源码的1/3。
开发时没问题,但是在上线生产时, 这个模板解析器太大且没有必要打包,为了精简与优雅,尤雨溪给我们提供了不同阶段使用不同版本的Vue。
关于不同版本的Vue:
1.vue.js 与vue.runtime.xxx.js的区别:
(1).vue.js是完整版的Vue,包含:核心功能+模板解析器。
(2).vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容
2.5 main.js中的render
为什么会用render不用template, 因为 默认引入的Vue =>
import Vue from 'vue'
是残缺版的,完整版在vue/dist/vue这个里面包含模板解析器。
默认脚手架生成的main.js入口文件
import Vue from "vue";
import App from "./App.vue";
// 创建vm
new Vue({
// 完成这样的功能:将App组件挂载到容器中。类似于 template:‘<App></App>’ 的作用,但这里不能使用template。
render: (h) => h(App)
}).$mount("#app");
// 关闭Vue生产提示
// Vue.config.productionTip = false
实际上等同于下列写法:
import Vue from "vue";
import App from "./App.vue";
new Vue({
el: "#app",
// createElement是一个形参
render(createElement) {
return createElement(App);
},
});
// Vue.config.productionTip = false
2.6 ref属性
ref是Vue提供的操作DOM的属性,相比于js中给标签添加id,在通过document.getElementById()
获取DOM,可以直接获取子组件标签的实例对象。作用如下:
- 被用来给元素或子组件注册引用信息(id的替代者)。
- 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)。
- 使用方式:
打标识:<h1 ref="xxx">.....</h1>
或<School ref="xxx"></School>
获取:this.$refs.xxx
<template>
<div>
<h1 ref="title">nihao</h1>
<School ref="school"></School>
<Student></Student>
<button @click="showDom">点我提示DOM</button>
</div>
</template>
<script>
import School from './components/School.vue';
import Student from './components/Student.vue';
export default {
components: {
School, Student
},
methods: {
showDom() {
console.log(this.$refs);
}
}
}
</script>
<style>
</style>
2.7 props配置
props功能是让组件接收外部传过来的数据。props是只读的
,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告。注意:由于props的渲染等级高于data,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
props适用于:
- 父组件 ==> 子组件 通信
- 子组件 ==> 父组件 通信 (要求父先给子一个函数)
注意:
- 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
- props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
父组件App
<template>
<div>
<Student name="zs" sex="男" :age="19"></Student>
</div>
</template>
子组件Student
<template>
<div>
<h2>{{ msg }}</h2>
<h1>学生名称:{{ name }}</h1>
<h1>学生性别:{{ sex }}</h1>
<h1>学生年龄:{{ age }}</h1>
</div>
</template>
<script>
export default {
// 1、简单声明接收
props: ['name', 'sex', 'age'],
// 2、接收的同时对数据类型进行限制
props:{
name:String,
age:Number,
sex:String
}
// 3、接收的同时对数据类型限制+默认值指定+必要性限制
props:{
name:{
type:String, // name的类型必须是String
required:true // name值是必要的
},
age:{
type:Number,
default:99
},
name:{
type:String,
required:true
}
}
data() {
return {
msg: 'hello,world'
}
}
}
</script>
<style>
</style>
2.8 mixin混入
mixin用于抽取公共的配置项为混入对象。通过import xxx from '...'导入
与 mixins:[xxx,...]
将抽取出来的配置与自身配置进行整合。
当配置出现冲突时,生命周期函数来者不拒,混合在前自身在后。除生命周期函数之外的配置以自身配置为主,覆盖掉混合配置。
EX:Student.vue与School.vue中都有公共的showName()方法,可以抽取为mixin.js
// mixin.js
export const mixin = {
// 除了methods,还可以配置data、 mounted等等诸多配置。
methods: {
showName() {
alert(this.name);
},
}
};
School.vue
<template>
<div>
<h1 @click="showName">学校名称:{{ name }}</h1>
<h1>学校地址:{{ address }}</h1>
</div>
</template>
<script>
// 1、引入
import { mixin } from '../mixin'
export default {
data() {
return {
name: '希望小学',
address: '北京北京'
}
},
// 2、通过mixins配置
mixins: [mixin]
}
</script>
<style>
</style>
Student.vue
<template>
<div>
<h2>{{ msg }}</h2>
<h1 @click="showName">学生名称:{{ name }}</h1>
<h1>学生性别:{{ sex }}</h1>
<h1>学生年龄:{{ age }}</h1>
</div>
</template>
<script>
import {mixin} from '../mixin'
export default {
props: ['name', 'sex', 'age'],
data() {
return {
msg: 'hello,world'
}
},
mixins:[mixin]
}
</script>
<style>
</style>
上述引用方式属于局部引用,下列配置属于全局引用,作用在main.js,全局混入会给vm下的所有vc添加响应的配置。
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// 全局引用
import {mixin} from './mixin'
Vue.mixin(mixin)
new Vue({
render: (h) => h(App),
}).$mount("#app");
2.9 插件
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。install(Vue,x,y,...)
与 main.js中先import导入,后Vue.use(plugins,1,2,...)
。
plugins.js
export default {
install(Vue) {
// 定义全局过滤器
Vue.filter("mySlice", function (value) {
return value.slice(0, 4);
});
// 定义全局指令
Vue.directive("fbind", {
// ...
});
// 定义全局混入
Vue.mixin({
//...
});
// 给Vue原型上添加一个hello方法(vm和vc就都能用了)
Vue.prototype.hello = () => {
alert("hello");
};
// 给Vue原型上添加一个属性(vm和vc就都能用了)
Vue.prototype.x = 100
},
};
main.js
import Vue from "vue";
import App from "./App.vue";
Vue.config.productionTip = false;
// 全局引用插件
import plugins from './plugins'
Vue.use(plugins)
new Vue({
render: (h) => h(App),
}).$mount("#app");
2.10 scoped样式
作用:让样式在局部生效,防止冲突。写法:<style scoped>
,编译时会打包成一个文件,可能出现类名一致样式冲突问题。
<style lang='less' scoped>
less相比css支持嵌套语句。使用less需要安装less-loader。
当前vue-cli创建Vue项目,webpack版本是4.xx.xx时,此时不能直接安装最新的less-loader版本,因为最新的less-loader是在webpack5的基础上适配的,应该安装less-loader 6~7之间的版本。
// 两条语句学习一下
// 查看目前发布的版本
npm view xxxx versions
// 安装指定版本的工具
npm install xxxx@版本号
三、组件通信
3.1 父传子
父组件向子组件通信采用props属性,详解本文:2.7props配置 章节。
3.2 子传父
- 通过父组件给子组件
传递函数类型的props
实现:子向父传值。
App.vue
<template>
<div>
<School :getSchoolName="getSchoolName"></School>
</div>
</template>
<script>
import School from './components/School.vue';
export default {
components: {
School
},
methods: {
getSchoolName(name) {
console.log('app接收到了', name);
}
// 接收多个参数,1、可以用ES6的新语法,将其他的参数自动包装成一个params数组 2、对个参数包装成对象
getSchoolName(name,...params) {
console.log('app接收到了', name,params);
}
}
}
</script>
School.vue
<template>
<div>
<h1>学校名称:{{ name }}</h1>
<h1>学校地址:{{ address }}</h1>
<button @click ="sendSchoolName">点击</button>
</div>
</template>
<script>
export default {
// props接收
props: ['getSchoolName'],
data() {
return {
name: '希望小学',
address: '北京北京'
}
},
methods: {
sendSchoolName() {
// 调用
this.getSchoolName(this.name)
}
}
}
</script>
- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,
使用@或者v-on
)。
通过this.$destroy() 可以销毁一个组件实例,当组件实例被销毁时,当前组件所有的自定义事件全部不奏效。
App.vue
<template>
<div>
//通过父组件给子组件绑定一个自定义事件实现,v-on等于@ v-on:getName效果一致 @事件名=“回调名”,二者可以一致
<School @getName="getSchoolName"></School>
// 只触发一次
<School @getName.once="getSchoolName"></School>
</div>
</template>
<script>
import School from './components/School.vue';
export default {
components: {
School
},
methods: {
getSchoolName(name) {
console.log('app接收到了', name);
}
}
}
</script>
School.vue
<template>
<div>
<h1>学校名称:{{ name }}</h1>
<h1>学校地址:{{ address }}</h1>
<button @click="sendSchoolName">点击</button>
</div>
</template>
<script>
export default {
data() {
return {
name: '希望小学',
address: '北京北京'
}
},
methods: {
sendSchoolName() {
// 触发School组件实例身上的getName事件
this.$emit('getName', this.name)
// 解绑
this.$off('getName') // 解绑一个自定义事件
this.$off(['getName','demo']) // 解绑对个自定义事件
this.$off() // 解绑所有的自定义事件
}
}
}
</script>
- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,
使用ref
)。
vue默认情况下,给组件添加的事件都认为是自定义事件,如果需要让vue解析为原生的DOM事件,事件后可采用
native
修饰,如:@click.native
App.vue
<template>
<div>
<School ref="school"></School>
</div>
</template>
<script>
import School from './components/School.vue';
export default {
components: {
School
},
methods: {
getSchoolName(name, ...params) {
console.log('app接收到了', name, params);
}
},
mounted(){
// 当vc挂载完毕,通过this.$refs.school即可获取实例对象,当school实例的getName方法被触发,便会调用vc的getSchoolName方法
// 这种方法的好处是可以加定时器,做一个延迟执行:比如延迟3秒触发子向父传递数据
// this.$refs.xxx.$on('自定义组件名',【这里的回调函数,谁调用‘getName’,this就是谁。不懂去看尚硅谷vue-P82】)
// 所以这里的回调要么配置在methods中,要么使用箭头函数this.$refs.xxx.$on('xx',()=>{}),否则this指向会出问题。
this.$refs.school.$on('getName',this.getSchoolName)
// 只触发一次
this.$refs.school.$once('getName',this.getSchoolName)
}
}
</script>
School.vue
<template>
<div>
<h1>学校名称:{{ name }}</h1>
<h1>学校地址:{{ address }}</h1>
<button @click="sendSchoolName">点击</button>
</div>
</template>
<script>
export default {
data() {
return {
name: '希望小学',
address: '北京北京'
}
},
methods: {
sendSchoolName() {
this.$emit('getName', this.name, 666, 888)
}
}
}
</script>
3.3 全局事件总线
一种组件间通信的方式,适用于任意组件间通信。
安装全局事件总线:main.js
new Vue({
......
beforeCreate(){
Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的vm
},
......
}).$mount("#app");
使用事件总线:
- 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods:{
demo(data){...}
}
......
mounted(){
this.$bus.$on('xxx',this.demo)
}
......
beforeDestroy(){
this.$bus.$off('xxx')
}
- 提供数据:
this.$bus.$emit('xxx',数据)
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
3.4 消息订阅与发布(pubsub)
一种组件间通信的方式,适用于任意组件间通信(适用各类前端框架,Vue使用较少)。
使用步骤:
- 安装第三方库pubsub:
npm i pubsub-js
- 在需要用的地方引入:
import pubsub from 'pubsub-js'
- 接收数据:A组件想要接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods:{
demo(data){...}
}
......
mounted(){
this.pid = pubsub.subscribe('xxx',this.demo) // 订阅消息,会创建一个id
}
......
beforeDestroy(){
pubsub.unsubscribe(this.pid)
}
- 提供数据:
pubsub.publish('xxx',数据)
- 最好在beforeDestroy钩子中,用
pubsub.unsubscribe(pid)
去取消订阅。
3.5 插槽
-
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件 。插槽就是一个占位符,存放父组件内部独有的html结构。
-
分类:默认插槽、具名插槽、作用域插槽
1、默认插槽:
<!-- 父组件中: -->
<Category>
<div>html结构1</div>
</Category>
<!-- 子组件Category.vue中: -->
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>
2、具名插槽:
<!-- 父组件中: -->
<Category>
<template slot="center">
<div>html结构1</div>
</template>
<template v-slot:footer>
<div>html结构2</div>
</template>
</Category>
<!-- 子组件中: -->
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>
3、作用域插槽:
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)。
父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category>
<Category>
<!-- 这里的 slot-scope 等价于 scope,写法不同 -->
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
子组件中:
<template>
<div>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>
四、Vuex
4.1 概念
在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
4.2 环境搭建
创建文件:src/store/index.js
//引入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
中创建vm时传入store
配置项
......
//引入store
import store from './store'
......
//创建vm
new Vue({
el:'#app',
render: h => h(App),
store
})
4.3 基本使用
src/store/index.js
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//引用Vuex
Vue.use(Vuex)
const actions = {
//响应组件中加的动作
jia(context,value){
// console.log('actions中的jia被调用了',miniStore,value)
context.commit('JIA',value)
},
}
const mutations = {
//执行加
JIA(state,value){
// console.log('mutations中的JIA被调用了',state,value)
state.sum += value
}
}
//初始化数据
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
4.4 getters的使用
-
概念:当state中的数据需要经过加工后再使用时,可以使用getters加工,类似于computed计算属性。
-
在
src/store/index.js
中追加getters
配置
......
const getters = {
bigSum(state){
return state.sum * 10
}
}
//创建并暴露store
export default new Vuex.Store({
......
getters
})
- 组件中读取数据:
$store.getters.bigSum
4.5 四个map方法
- mapState方法:用于帮助我们映射
state
中的数据为计算属性。
computed: {
//借助mapState生成计算属性:sum、school、subject(对象写法)
...mapState({sum:'sum',school:'school',subject:'subject'}),
//借助mapState生成计算属性:sum、school、subject(数组写法)
...mapState(['sum','school','subject']),
},
- mapGetters方法:用于帮助我们映射
getters
中的数据为计算属性。
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
- mapActions方法:用于帮助我们生成与
actions
对话的方法,即:包含$store.dispatch(xxx)
的函数。
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
}
- mapMutations方法:用于帮助我们生成与
mutations
对话的方法,即:包含$store.commit(xxx)
的函数。
methods:{
//靠mapActions生成:increment、decrement(对象形式)
...mapMutations({increment:'JIA',decrement:'JIAN'}),
//靠mapMutations生成:JIA、JIAN(对象形式)
...mapMutations(['JIA','JIAN']),
}
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。
4.6 模块化
- 目的:让代码更好维护,让多种数据分类更加明确。
- 修改
src/store/index.js
,必须开启namespaced命名空间。这里的countAbout、personAbout可以抽取成单独的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'}),
五、vue-router
- 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
- 前端路由:key是路径,value是组件。
5.1 基本使用
-
安装vue-router,命令:
npm i vue-router
-
在main.js中应用插件:
Vue.use(VueRouter)
-
编写router配置项:
//引入VueRouter
import VueRouter from 'vue-router'
//引入Luyou 组件
import About from '../components/About'
import Home from '../components/Home'
//创建router实例对象,去管理一组一组的路由规则
const router = new VueRouter({
routes:[
{
path:'/about',
component:About
},
{
path:'/home',
component:Home
},
//....
// 重定向,在项目跑起来的时候,访问 / ,立刻让它定向到首页
{
path:'*',
redirect:'/home'
}
]
})
//暴露router
export default router
- 实现切换(active-class可配置高亮样式):
<router-link active-class="active" to="/about">About</router-link>
- 指定展示位置:
<router-view></router-view>
- 注意点:
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息。- 整个应用只有一个router,可以通过组件的
$router
属性获取到。
- 路由组件与非路由组件的区别
- 路由组件一般放置在pages | views文件夹,非路由组件一般放置components文件夹中。
- 路由组件一般需要在router文件夹中进行注册(使用的即为组件的名字),非路由组件在使用的时候,一般都是以标签的形式使用。
- 注册完路由,不管路由路由组件、还是非路由组件身上都有$route、$router属性。
$route:一般获取路由信息 ==>[路径、query、params等等]
$router:一般进行编程式导航进行路由跳转 ==>[push|replace]
- 防止连续点击多次路由报错,需要重写push | replace方法。
// 1、若依写法:
// 防止连续点击多次路由报错
let routerPush = Router.prototype.push;
Router.prototype.push = function push(location) {
return routerPush.call(this, location).catch((err) => err);
};
call与apply的区别
相同点:都可以调用函数一次,都可以篡改函数的上下文一次。
不同点:call传递参数用逗号隔开,apply方法执行传递数组。
// 2、尚硅谷写法:
// 防止连续点击多次路由报错
let originPush = Vue.prototype.push;
let originReplace = Vue.prototype.Replace;
// 第一个参数:告诉原来的push方法,往哪跳(传递那些参数)
// 第二个参数:成功的回调
// 第三个参数:失败的回调
Vue.prototype.push = function (localtion,resolve,reject){
if(resolve && reject){
originPush.call(this,localtion,resolve,reject);
}else{
originPush.call(this,localtion,()={},()={});
}
}
Vue.prototype.replace = function (localtion,resolve,reject){
if(resolve && reject){
originReplace.call(this,localtion,resolve,reject);
}else{
originReplace.call(this,localtion,()={},()={});
}
}
5.2 多级路由
- 配置路由规则,使用children配置项:
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]
- 跳转(要写完整路径):
<router-link to="/home/news">News</router-link>
5.3 路由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
5.4 路由params传参
注意:路由传递参数的时候,对象的写法可以是name、path形式,但是,path这种写法不能与params参数一同使用
。
// 这种path与params混合的写法,报错!
this.$router.push{
path:'/search',
params:{
keyword:this.keyword
},
query:{
name:this.name
}
}
如果路由要求传递params参数,但是不传递时,URL会有问题导致无法跳转。如需指定params参数可传可不传,在配置路由router/index.js
时,在占位的后面加上一个问号即可。
{
name:'xiangqing',
path:'detail/:id?', //通过问号匹配参数可传可不传
component:Detail
}
当params参数可传可不传时,如果传递的是空串,可能导致URL路径缺失,这里可以用undefined
解决
// 跳转
this.$router.push{
path:'/search',
params:{
id:'' || undefined // 加一个undefined
}
}
// router/index.js
// ......
{
name:'xiangqing',
path:'detail/:id?', //通过问号匹配参数可传可不传
component:Detail
}
- 配置路由,声明接收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>
- 接收参数:
$route.params.id
$route.params.title
5.5 路由命名
作用:可以简化路由的跳转。
具体使用
{
path:'/demo',
component:Demo,
children:[
{
path:'test',
component:Test,
children:[
{
name:'hello' //给路由命名
path:'welcome',
component:Hello,
}
]
}
]
}
<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 -->
<router-link
:to="{
name:'hello',
query:{
id:666,
title:'你好'
}
}"
>跳转</router-link>
5.6 路由props配置
作用:让路由组件更方便的收到参数。
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
// 第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
// props:{a:900}
// 第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
// 组件直接用props:['id','title']配置即可使用,但是只适用于params传参
// props:true
// 第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
// 都适用,props中可以接收route对象,这里可以用结构赋值props({ query:{id,title} }){ return{ id,title } }
props(route){
return {
id:route.query.id,
title:route.query.title
}
}
}
5.7 链接跳转replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式。
- 浏览器的历史记录有两种写入方式:分别为
push
和replace
,push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
。 - 如何开启
replace
模式:<router-link replace .......>News</router-link>
。
5.8 编程式路由导航
-
作用:不借助
<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() //可前进也可后退
5.9 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁。
// 这里的include属性可以接收数组形式,:include="['News','xxx组件名']",后面的是组件名。
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
5.10 路由组件生命周期函数
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
activated
路由组件被激活时触发。deactivated
路由组件失活时触发。
5.11 路由守卫
-
作用:对路由进行权限控制。
-
分类:全局守卫、独享守卫、组件内守卫。
-
全局守卫,配置在
router/index.js
文件内部下方。
//......
//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
console.log('beforeEach',to,from)
if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
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'
}
})
- 独享守卫,配置到各个路由中。
//......
{
name:'xiangqing',
path:'detail/:id',
component:Detail,
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()
}
}
}
//......
- 组件内守卫,配置在xxx组件中。
//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
5.12 hash与history工作模式
-
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。localhost:5000/#/xxxx(hash值)
-
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
-
hash模式:
- 地址中永远带着#号,不美观 。
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
- 兼容性较好。
- history模式:
- 地址干净,美观 。
- 兼容性和hash模式相比略差。
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
5.13 meta元数据
我们经常会在进入一个页面时判断是否已经登陆,经常会用到路由导航守卫router.beforeEach(to,from, next),一个两个页面还好,但是多的话,就会麻烦,并且路由还会嵌套。这时可以使用meta。
在配置路由时,经常会用到path、name、component,还有一个就是meta 元数据,给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。经常用它来做登录校验。
{
path: '/imgMove/:id',
name: 'imgMove',
meta: {
requiresAuth: true
},
component: imgMove
},
{ //作品页面
path: '/work',
name: 'work',
meta: {
canNotLogin: true
},
component: work
},
我们需要校验判断item下面的meta对象的requiresAuth是不是true,就可以进行一些限制。
router.beforeEach((to, from, next) => {
if (to.matched.some(function (item) {
return item.requiresAuth
})) {
next('/login')
} else
next()
})