Vue学习

基础知识

el与data的两种写法

el的写法

  1. 第一种写法:直接声明el

    1
    2
    3
    const vm=new Vue({
    el:'#root'
    })
  2. 第二种写法:使用$mount进行声明

    1
    2
    3
    4
    const vm=new Vue({

    })
    vm.$mount('#root');

data的写法

  1. 第一种写法:对象式

    1
    2
    3
    4
    const vm=new Vue({
    el:'#root',
    data:{}
    })
  2. 第二种写法:函数式

    1
    2
    3
    4
    5
    6
    7
    8
    const vm=new Vue({
    el:'#root',
    data(){
    return {

    }
    }
    })

    PS:data不能写成箭头函数式 【data:()=>{}】,因为箭头函数this没有内置对象,向外找的时候,会造成引用对象变为window

MVVM

  1. M:模型(Model)【对应data中的数据】
  2. V:视图(View)【模板】
  3. VM:视图模型(ViewModel)【Vue实时对象】

数据代理

Object.defineProperty

1
2
3
4
5
6
7
8
9
10
11
12
13
Object.defineProperty(要给哪个对象添加,添加的字段名,添加的配置项)
添加的配置项:{
value:值,
enumerable:true,//控制属性是否可以枚举,默认值是false
writable:true,//控制属性是否可以被修改,默认值是false
configurable:true //控制属性是否可以被删除,默认值是false
//高级用法
//当有人读取对象添加的字段名的时候,get函数(getter)就会被调用,且返回值就是age的值
get(){
//可以添加方法
}
////当有人修改对象添加的字段名的时候,set函数(setter)就会被调用,且会收到修改的具体值
}

PS:添加的数据不可以被枚举(遍历)

理解数据代理

**数据代理:**通过一个对象代理对另一个对象中属性的操作(读/写)

image-20211217153650572

**基本原理:**通过Object.defineProperty()把data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter。在getter/setter内部去操作(读/写)data中对应的属性

事件处理

事件的基本使用

  1. 使用v-on:xxx或者@xxx绑定事件,其中xxx是事件名;【绑定的事件可以写一些简单的表达式】
  2. 事件的回调需要配置在methods对象中,最终会在vm上
  3. methods中配置的函数,不需要箭头函数!否则this就不是vm了
  4. methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象;
  5. @click=”demo”和@click=”demo($event)效果一致,但后者可以传参”【@click=”demo(number)会替换掉event对象,但是可以写成demo(number,$event),就不会丢失event对象】

事件修饰符

==常用的按键别名==

  1. **prevent:**阻止默认事件(常用)
  2. **stop:**阻止事件冒泡(常用)
  3. **once:**事件只触发一次(常用)
  4. **capture:**使用事件的捕获模式
  5. **self:**只有event.target是当前操作的元素时才能触发事件
  6. **passive:**事件的默认行为立即执行,无需等待事件回调执行完毕

键盘事件

  1. ==常用的键盘别名==
    • 回车===>enter
    • 删除===>delete【捕获删除退格键】
    • 退出===>esc
    • 空格===>space
    • 换行===>tab【特殊,必须配合keydown去使用】
    • 上===>up
    • 下===>down
    • 左===>left
    • 右===>right
  2. Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)
  3. 系统修饰符(用法特殊):==ctrl、alt、shift、meta==
    1. 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才能触发
    2. 配合keydown使用:正常触发事件
  4. 也可以使用keyCode去指定具体的按键(不推荐)
  5. Vue.config.keyCodes.自定义键名=键码,可以去定制按键别名

计算属性与监视属性

计算属性(computed)

基本用法

  1. **定义:**由已知属性计算出来的全新属性叫做计算属性
  2. **原理:**底层借助了Object.defineproperty方法提供的getter和setter
  3. get什么时候调用?
    1. 初次读取xxx时
    2. 所依赖的数据发生变化时
  4. set什么时候调用?
    • 当xxx会被修改成另一个值的时候【比如this.xxx=”李四”】,如果确定不会被修改,set可以不写
  5. **优势:**与methods实现相比,内部有缓存机智(复用),效率更高,测试方便
  6. 备注:
    1. 计算属性最终会出现在vm上,直接读取使用即可
    2. 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生变化
1
2
3
4
5
6
7
8
9
10
11
computed:{
xxx:{
//get有什么作用?当有人读取xxx时,get就会被调用,且返回值就作为xxx的值
get(){

}
set(value){

}
}
}

计算属性的简写

如果不会用到set方法,可以使用简写

1
2
3
4
5
coputed:{
xxx(){
//相当于get方法
}
}

监视属性(watch)

基本用法

  1. 当被监视的属性变化时,回调函数自动调用,进行相关操作
  2. 监视的属性必须存在,才能进行监视!!
  3. 监视的两种写法
    1. new Vue时传入watch配置
    2. 通过vm.$watch监视
1
2
3
4
5
6
7
8
9
10
watch: {
//要监视的属性
xxx:{
immediate:true,//初始化时让handler调用一下
//当xxx发生改变时,handler会被调用
handler(newValue,oldValue){

}
}
}

深度监视

  1. Vue中的watch默认不监测内部值得改变(一层)
  2. 使用deep:true可以监测对象内部值得改变(多层)

PS:

  1. Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
  2. 使用watch时根据数据的具体结构,决定是否采用深度监视
1
2
3
4
5
6
7
8
9
10
watch: {
xxx:{
//监视多级结构中某个属性的变化,默认是false
deep:true,
immediate:true,
handler(newValue,oldValue){

}
}
}

监视属性的简写

当配置项里面只有handler的时候,可以使用简写

1
2
3
4
watch: {
xxx(newValue,oldValue){
}
}

coputed和watch之间的区别

  1. computed能完成的功能,watch都可以完成

  2. watch能完成的功能,computed不一定能完成。例如:watch可以进行异步操作

两个重要的小原则:

  1. 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
  2. 所有不被Vue所管理的函数(定时器的回调函数,ajax的回调函数等),最好都写成箭头函数。这样this的指向才是vm组件实例对象

常见渲染

条件渲染

1.v-ifv-show使用场景:

如果需要高频率刷新页面,则优先使用v-show

2.v-ifv-else-ifv-else可用作条件渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<body>
<div id="root">
<h2>当前n的值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<div v-if="n < 2">Angular</div>
<div v-else-if="n == 2">React</div>
<div v-else>Vue</div>
</div>
</body>
<script type="text/javascript">
const vm=new Vue({
el:"#root",
data:{
n:0
}
})
</script>
</html>

注意点:

  1. v-ifv-else-if中间不能有间断,否则会报错(v-if必须作为开头)

    1
    2
    3
    4
    //此种情况会报错
    <div v-if="n < 2">Angular</div>
    <div>@@@</div>
    <div v-else-if="n == 2">React</div>
  2. template标签只能与v-if配合使用,不能与v-show配合使用

  3. 使用v-if的时候,元素不一定能获取到,但是使用v-show的时候一定能获取到

列表渲染

基本列表

v-for指令

  1. 用于展示列表数据
  2. 语法:v-for=”(item,index) in xxx” :key=”yyy”
  3. 可遍历:数组,对象,字符串,指定次数

基础案例

人员列表案例(遍历数组)

效果图如下:

image-20210907132503357

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<div id="root">
<h2>人员列表</h2>
<ul>
<li v-for="(item,index) in personsinfo" :key="index">{{item.name}}---{{item.age}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip=false
const vm=new Vue({
el:"#root",
data:{
personsinfo:[
{id:"001",name:"张三",age:"18"},
{id:"002",name:"李四",age:"19"},
{id:"003",name:"王五",age:"20"},
]
}
})
</script>

v-for可以用于对数组进行遍历

车列表案例(遍历对象)

效果图如下:

image-20210907134419886

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<div id="root">
<h2>汽车信息</h2>
<ul >
<li v-for="(value,key) in car" :key="key">{{key}}---{{value}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
car: {
name: "奥迪A8",
price: "70万",
color: "黑色"
}
}
})
</script>

注意value和key遍历的位置顺序,其中value放在前,key放在后面

遍历字符串案例(用的少)

效果图如下:

image-20210907135104055

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<div id="root">
<h2>遍历字符串</h2>
<ul >
<li v-for="(char,index) in str" :key="index">{{char}}---{{index}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
str:"hello"
}
})
</script>
遍历指定次数案例(用的少)

效果图如下:

image-20210907135351385

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<div id="root">
<h2>遍历指定次数</h2>
<ul >
<li v-for="(item,index) in 5" :key="index">{{item}}---{{index}}</li>
</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
}
})
</script>

属性讲解

1.key

key属性属于vue中较特殊的属性,相当于让每一个li标签都有了一个唯一的标识,相当于身份证。每当进行遍历操作生成同样结构的数据时,需要给这些同样结构的数据绑定特殊的标识

2.item

遍历出的每组数据

3.index

索引值

key的原理

虚拟Dom对比算法

以index作为key

image-20210908103917739

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<body>
<div id="root">
<h2>人员列表</h2>
<button @click.once="addOne">准备添加一个老刘</button>
<ul>
<li v-for="(item,index) in personsinfo" :key="index">
{{item.name}}---{{item.age}}
<input type="text" />
</li>

</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
personsinfo: [
{ id: "001", name: "张三", age: "18" },
{ id: "002", name: "李四", age: "19" },
{ id: "003", name: "王五", age: "20" },
],
},
methods: {
addOne() {
const laoliu = { id: "004", name: "老刘", age: "25" };
this.personsinfo.unshift(laoliu)
}
}
})
</script>

以id作为key

image-20210908104259333

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<body>
<div id="root">
<h2>人员列表</h2>
<button @click.once="addOne">准备添加一个老刘</button>
<ul>
<li v-for="(item,index) in personsinfo" :key="item.id">
{{item.name}}---{{item.age}}
<input type="text" />
</li>

</ul>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
personsinfo: [
{ id: "001", name: "张三", age: "18" },
{ id: "002", name: "李四", age: "19" },
{ id: "003", name: "王五", age: "20" },
],
},
methods: {
addOne() {
const laoliu = { id: "004", name: "老刘", age: "25" };
this.personsinfo.unshift(laoliu)
}
}
})
</script>

总结

  1. 如果进行过破坏顺序操作,使用index作为key会出现错乱
  2. 对比算法对比的是虚拟Dom,不是真实Dom
  3. 以index作为key,效率较低(因为对比不成功的情况下,会需要生成多个新的节点)
  4. key中没有给定值的时候,默认使用索引作为key(所以不写key的时候,也出现了第一种案例【以index作为key】中出现错乱的情形)

面试题

react vue中的key有什么作用(key的内部原理)

  1. 虚拟DOM中key的作用:

    key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异对比,对比规则如下:

    • 旧虚拟DOM中找到了与新虚拟DOM中相同的key:

      (1)若虚拟DOM中内容没变,直接使用之前的真实DOM!

      (2)若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。

    • 旧虚拟DOM中未找到与新虚拟DOM相同的key

    ​ 创建新的真实DOM,随后渲染到页面。

  2. 用index作为key可能会引发的问题:

    • 若对数据进行:逆序添加,逆序删除等破坏顺序操作:

      ​ 会产生没有必要的真实DOM更新==》界面效果没问题,但效率低

    • 如果结构中还包含输入类的DOM:

      ​ 会残生错误DOM更新===》界面有问题

  3. 开发中如何选择key?

    1. 最好使用每条数据的唯一表示作为key,例如id,手机号,学号等唯一值。
    2. 如果不存在对数据进行逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。

指令

内置指令

自定义指令

函数式

举例

如果想自定义一个指令,点击按钮,使得值放大10倍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!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">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!--引入 element-ui 的样式-->
<link rel="stylesheet" type="text/css" href="./css/elementui.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="./js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="./js/elementui.js"></script>
<title>Document</title>
</head>

<body>
<div id="root">
<h2>{{name}}</h2>
<h2>当前的值是:<span v-text="n"></span></h2>
<h2>放大10倍的值是:<span v-big="n"></span></h2>
<el-button @click="n++">给我大大大</el-button>
</div>
</body>
<script type="text/javascript">

Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
name:"嘀嘀嘀",
n:1
},
directives:{
/**
* element:真实DOM binding:本次与真实DOM绑定的信息
* */
big(element,binding){
element.innerText=binding.value*10
}
}


})
</script>

</html>

注意:

自定义指令函数什么时候会被调用?

  1. 指令与元素成功绑定时(一上来时)
  2. 指令所在的模板被重新解析时(例如data中的name被进行修改后,该函数又被调用了)

对象式

举例

定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!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">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!--引入 element-ui 的样式-->
<link rel="stylesheet" type="text/css" href="./css/elementui.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="./js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="./js/elementui.js"></script>
<title>Document</title>
</head>

<body>
<div id="root">
<h2>当前的值是:<span v-text="n"></span></h2>
<hr/>
<!-- <el-input v-fbind:value="n"></el-input> -->
<input type="text" v-fbind:value="n"></input>
<el-button @click="n++">给我大大大</el-button>
</div>
</body>
<script type="text/javascript">
/**
* 定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点[使用函数式无法实现]
* */
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
n:1
},
directives:{
fbind:{
//指令与元素成功绑定时(一上来)【样式,value值,绑定事件之类的】
bind(element,binding){
element.value=binding.value;
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus();
},
//指令所在的模板被重新解析时
update(element,binding){
element.value=binding.value;
}
}
}


})
</script>

</html>

注意:

需要指令元素被插入页面(即获得父元素时),才推荐使用对象式

Vue监视数据的原理

对象

Vue监视数据的改变靠得就是其中的getter,setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!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">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!--引入 element-ui 的样式-->
<link rel="stylesheet" type="text/css" href="./css/elementui.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="./js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="./js/elementui.js"></script>
<title>Document</title>
</head>

<body>
<div id="root">
<h2>人员列表</h2>
<h2>学生姓名:{{student.name}}</h2>
<h2>学生年龄:{{student.age}}</h2>
<h2 v-if="student.sex">学生性别:{{student.sex}}</h2>
<el-button @click="updateMa">更新学生信息并添加性别</el-button>
<hr/>
</div>
</body>
<script type="text/javascript">
/**
*通过按钮直接修改data中的数据不会渲染
* */
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
myInput: "",
student: { name: "张三", age: "99" }
},
methods: {
updateMa() {
this.student.name="李四"
this.$set(this.student,"sex","男")
}
}
/* computed:{
storagList(){
return this.personsinfo.filter(item=>item.name.indexOf(this.myInput)!=-1)

}
}, */

})
</script>

</html>

这时候点击按钮是会渲染的,因为对象中匹配了getter,setter方法

PS:问一个问题【vm._ data.student===vm.student,此时vm._ data=vm不?】

答案是不等于,_data做了数据加工

数组

Vue没有直接为数组的索引匹配setter和getter方法,这时就会出现一个问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<!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">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!--引入 element-ui 的样式-->
<link rel="stylesheet" type="text/css" href="./css/elementui.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="./js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="./js/elementui.js"></script>
<title>Document</title>
</head>

<body>
<div id="root">
<h2>人员列表</h2>
<el-input style="width: 300px;" v-model="myInput"></el-input>
<el-button @click="updateMa">更新马冬梅的信息</el-button>
<ul>
<li v-for="(item,index) in personsinfo" :key="item.id">
{{item.name}}---{{item.age}}---{{item.sex==="0"?"男":"女"}}
</li>

</ul>
<hr/>
<h2>学生姓名:{{student.name}}</h2>
<h2>学生年龄:{{student.age}}</h2>
</div>
</body>
<script type="text/javascript">
/**
*通过按钮直接修改data中的数据不会渲染
* */
Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
myInput: "",
personsinfo: [
{ id: "001", name: "马冬梅", age: "18", sex: "1" },
{ id: "002", name: "周冬雨", age: "19", sex: "1" },
{ id: "003", name: "周杰伦", age: "20", sex: "0" },
{ id: "004", name: "周杰伦打球", age: "20", sex: "0" },
],
student: { name: "张三", age: "99" }
},
methods: {
updateMa() {
this.personsinfo[0] = { id: "001", name: "杰克吗", age: "30", sex: "2" }

}
}
/* computed:{
storagList(){
return this.personsinfo.filter(item=>item.name.indexOf(this.myInput)!=-1)

}
}, */

})
</script>

</html>

这时候点击按钮,就无法渲染修改马冬梅的信息(Vue没有直接为数组的索引匹配setter和getter方法,需要使用数组的方法,例如【push,pop,shife,unshift,splice,soft.reverse】等方法去修改)

image-20211114161701900

PS:

  1. Vue将被侦听的数组的变更==方法==进行了包裹,所以它们特将触发视图更新
  2. 通过==vm.$set(vm.personsinfo,0,{ id: “001”, name: “杰克吗”, age: “30”, sex: “2” })==也可以修改,但是使用的不多

总结

Vue监视数据的原理:

  1. Vue会监视data中==所有层次==的数据

  2. 如何监测对象中的数据?

    通过setter实现监听,且要在new Vue时传入要监测的数据

    1. 对象中==后追加==的属性,Vue默认==不做==响应式处理
    2. 如需给后续的属性做响应式,请使用如下API:
      • Vue.set(target,propertyName/index,value)
      • vm.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据?

    通过包括数组更新元素的方法实现,本质就是做了两件事

    1. 调用原生对应的方法对数组进行更新
    2. 重新解析模板,进而更新页面
  4. 在Vue修改数组的某个元素一定要用如下方法

    1. 使用这些API:**push()pop()shift()unshift()splice()sort()reverse()**、
    2. Vue.set()vm.$set() 例如:==vm.$set(vm.personsinfo,0,{ id: “001”, name: “杰克吗”, age: “30”, sex: “2” })==
  5. **特别注意:**Vue.set()和vm.$set()不能给vm或vm的根数据对象添加属性!!!例如vm.$set(vm,”sex”,”男”)会报错

生命周期

案例:将“欢迎学习Vue”的实现透明度变为0后,又变为1,形成动画效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!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">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!--引入 element-ui 的样式-->
<link rel="stylesheet" type="text/css" href="./css/elementui.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="./js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="./js/elementui.js"></script>
<title>Document</title>
</head>

<body>
<div id="root">
<h2 :style="{opacity:opacity}">欢迎学习Vue</h2>
</div>
</body>
<script type="text/javascript">

Vue.config.productionTip = false
const vm = new Vue({
el: "#root",
data: {
opacity: 1,
},
mounted() {
const self = this;
setInterval(() => {
debugger
self.opacity = self.subtr(self.opacity,0.01)
if (self.opacity <= 0) self.opacity = 1;
}, 16)
},
methods: {
subtr(arg1, arg2) {
var r1, r2, m, n;
try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
m = Math.pow(10, Math.max(r1, r2));
n = (r1 >= r2) ? r1 : r2;
return ((arg1 * m - arg2 * m) / m).toFixed(n);
}
}

})
</script>

</html>

注意:从这个案例中我们可以看到“生命周期”的作用:即在某一个时刻会自动执行的代码

定义:

  1. 又名:生命周期函数、生命周期函数、生命周期钩子
  2. 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
  3. 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
  4. 生命周期函数中的this指向是vm或者组件实例对象

包括:

  • beforeCreate ==将要创建==
  • created ==创建完毕==
  • beforeMount:已经解析完了模板,但是还没来及往页面上放 ==将要挂载==
  • mounted:Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted 【重要】 ==挂载完毕==
  • **beforeUpdate ** ==将要更新==
  • **updated ** ==更新完毕==
  • beforeDestroy:此时vm中的data、methods、指令等等都处于可用状态。一般在这时候:关闭定时器、取消订阅消息、解绑自定义事件等==收尾操作== 【重要】 ==将要销毁==
  • **destroyed ** ==销毁完毕==

生命周期原理图

总结:

  • 常用的生命周期钩子:
    1. mounted:发送ajax请求,启动定时器、绑定自定义事件、订阅消息等【初始化操作】
    2. beforeDestroy:清除定时器、解除自定义事件、取消订阅消息等【收尾工作】
  • 关于销毁Vue实例
    1. 销毁后借助Vue开发者工具看不到任何信息
    2. 销毁后自定义事件会失效,但原生DOM事件依然有效
    3. 一般不会在beforeDestroy操作数据,因为即使操作数据,也不会再触发更新流程了

Vue组件化编程

传统方式编写应用

缺点:

  1. 依赖关系混乱不好维护
  2. 代码复用率不高

传统方式编写应用

组件化方式编程

组件方式编写应用

定义:实现应用中==局部==功能==代码==(css/html/js)和==资源==(mp3/mp4/ttf/zip/image)的==集合==(html/css/js/image……)

组件的定义

配置组件的时候需要用到:const XXX=Vue.extend({配置项});

注意:

  1. 在创建组件的时候,data必须使用==函数式==,否则会报错(究其根本,是因为组件复用时,对象式是引用对象,地址指向同一块区域,容易一个数值变,全部变)
  2. 由Vue管理的函数,一定不要写箭头函数,一旦写箭头函数,this就不再是Vue实例了

组件化编程的步骤:

  1. 创建组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//创建学校组件
const schoolVM = Vue.extend({
//el: "#root", //组件定义时一定不要写el配置项。因为最终所有的组件都要被一个vm管理,由vm决定服务于谁
/* data: {
//对象式写法,浏览器会报错(究其根本,是因为组件复用时,对象式是引用对象,地址指向同一块区域,容易一个数值变,全部变)
}, */
template:`
<div>
<h2>学校名称:{{schoolname}}</h2>
<h2>学校地址:{{schooladdr}}</h2>
</div>
`,
data(){
return{
schoolname:"宇宙大学",
schooladdr:"宇宙街一号"
}
}

})
//创建学生组件
const studentVM = Vue.extend({
template:`
<div>
<h2>学生名称:{{studentname}}</h2>
<h2>学生年龄:{{studentage}}</h2>
</div>
`,
data(){
return{
studentname:"张三",
studentage:18
}
}

})
  1. 注册组件(示例:局部注册)
1
2
3
4
5
6
7
8
9
const vm = new Vue({
el: "#root",
//局部注册
components:{
school:schoolVM,
student:studentVM

}
})
  1. 编写组件标签(组件标签名字除首字母外不能为大写,否则浏览器会报错【html】)
1
2
3
4
5
<div id="root">
<school></school>
<hr>
<student></student>
</div>

总结:

  1. 如何定义一个组件?

    使用Vue.enxtend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但是区别如下:

    1. el不要写,为什么? —–因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务那个容器

    2. data必须写成函数式,为什么? —-避免组件被服用时,数据存在引用关系

      **备注:**使用template可以配置组件结构

  2. 如何注册组件

    1. 局部注册:靠new Vue时候传入components选项
    2. 全局注册:靠Vue.component(‘组件名’,组件)
  3. 编写组件标签

几个注意点:

  1. 关于组件名:

    1. 一个单词组成:
      1. 第一种写法(首字母小写):school
      2. 第二种写法(首字母大写):School
    2. 多个单词组成
      1. 第一种写法(keyab-case命名):my-school
      2. 第二种写法(CamelCase命名):MySchool(需要脚手架支持)
    3. 备注:
      1. 组件名尽可能回避HTML中已有的元素名称,例如:h1,h2都不行
      2. 可以使用name配置项执行组件在开发者工具中呈现的名字
  2. 关于组件标签:

    1. 第一种写法:

    2. 第二种写法:

      备注:不使用脚手架时,会导致后续组件不能渲染

  3. 一个简写方式

    ​ const school=Vue.extend(options)可简写为: const school=options

非单文件组件(了解)

定义:一个文件中包含有n个组件

局部注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<!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">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!--引入 element-ui 的样式-->
<link rel="stylesheet" type="text/css" href="./css/elementui.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="./js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="./js/elementui.js"></script>
<title>Document</title>
</head>

<body>
<div id="root">
<school></school>
<hr>
<student></student>
</div>
</body>
<script type="text/javascript">

Vue.config.productionTip = false
//创建学校组件
const schoolVM = Vue.extend({
//el: "#root", //组件定义时一定不要写el配置项。因为最终所有的组件都要被一个vm管理,由vm决定服务于谁
/* data: {
//对象式写法,浏览器会报错(究其根本,是因为组件复用时,对象式是引用对象,地址指向同一块区域,容易一个数值变,全部变)
}, */
template:`
<div>
<h2>学校名称:{{schoolname}}</h2>
<h2>学校地址:{{schooladdr}}</h2>
</div>
`,
data(){
return{
schoolname:"宇宙大学",
schooladdr:"宇宙街一号"
}
}

})
//创建学生组件
const studentVM = Vue.extend({
template:`
<div>
<h2>学生名称:{{studentname}}</h2>
<h2>学生年龄:{{studentage}}</h2>
</div>
`,
data(){
return{
studentname:"张三",
studentage:18
}
}

})
const vm = new Vue({
el: "#root",
//局部注册
components:{
school:schoolVM,
student:studentVM

}
})
</script>

</html>

全局注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!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">

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<!--引入 element-ui 的样式-->
<link rel="stylesheet" type="text/css" href="./css/elementui.css">
<!-- 必须先引入vue 后使用element-ui -->
<script src="./js/vue.js"></script>
<!-- 引入element 的组件库-->
<script src="./js/elementui.js"></script>
<title>Document</title>
</head>

<body>
<div id="root">
<!--第三步,使用标签-->
<hello></hello>
</div>
</body>
<script type="text/javascript">

Vue.config.productionTip = false
//第一步:创建组件
const hello = Vue.extend({
template:`
<div>
<h2>你好啊,{{name}}</h2>
</div>
`,
data(){
return{
name:"嘀嘀嘀",
}
}

})
//第二步:全局注册组件
Vue.component("hello",hello)
const vm = new Vue({
el: "#root",
data: {

},


})
</script>

</html>

单文件组件(重要)

定义:一个文件中只包含有1个组件

MySchool.vue(组件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<!--组件的结构-->
<div class="demo">
<h2>学校名称:{{ schoolname }}</h2>
<h2>学校地址:{{ schooladdr }}</h2>
<button @click="showName">点我显示学校名</button>
</div>
</template>
<script>
export default {
name: "MySchool",
data() {
return {
schoolname: "宇宙大学",
schooladdr: "宇宙街一号",
};
},
methods: {
showName() {
alert(this.schoolName);
},
},
};
</script>

<style>
/**组件的样式 */
.demo {
background-color: yellow;
}
</style>

App.vue(组件中“一人(vm)之上,万人(所有的组件)之下”的存在)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>
<MySchool></MySchool>
</div>
</template>

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

}
</script>

<style>

</style>

main.js

1
2
3
4
5
6
7
8
import App from './App.vue'

new Vue({
el:"#root",
components:{App},
})


index.html(入口文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">

<title>练习一下单文件组件的语法</title>
</head>
<body>
<div id="root">
<App></App>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="./main.js"></script>
</body>
</html>

VueComponent(重要)

image-20211229135323804

关于VueComponent:

  1. school组件本质是一个名为==VueComponent的构造函数==,且不是程序员定义的,时Vue.extend生成的。
  2. 我们只需要写<school/>或者<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的是:==new VueComponent(options)==
  3. **特别注意:每次调用Vue.extend,返回的都是一个全新的**VueComponent!!!
  4. 关于this的指向
    1. 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是==【VueComponet实例对象】==
    2. new Vue(options)配置中:data函数、methodsmethods中的函数、watch中的函数、computed中的函数,他们的this均是==【Vue实例对象】==
    3. VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm

一个重要的内置关系

1
2
3
4
5
6
7
8
9
10
11
12
13
 <script type="text/javascript">

Vue.config.productionTip = false
function Demo(){
this.a=1
this.b=2
}
//创建一个Demo的实例对象
const d=new Demo
console.log(Demo.prototype) //显示原型属性==可以操作原型对象
console.log(d.__proto__)//隐式原型属性
Demo.prototype.x=99 //追加x属性
</script>

PS:需要复习原型知识(对象上有_ proto_ ,属性上有prototype,指向的都是缔造者的原型对象)

一个重要的内置关系:VueComponent.prototype._ proto_ === Vue.prototype(VueComponet的原型对象的原型对象等于Vue的原型对象)

为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法

使用Vue脚手架

Vue脚手架是Vue官方提供的标准化开发工具(开发平台)

初始化脚手架

具体步骤

  1. 第一步(仅第一次执行):全局安装@vue/cli

    1
    npm install -g @/vue/cli
  2. 第二步:**切换到你要创建项目的目录**,然后使用命令创建项目

    1
    vue create XXXXX

    image-20211114170037232

    这是我开始定义好的环境【Vue3 node-sass,babel,router,vuex,eslint】

  3. 启动项目

    1
    npm run serve

备注:

1
2
3
4
5
1. 如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry 

https://registry.npm.taobao.org

2. Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置, 请执行:vue inspect > output.js

render函数

Vue-cli引入的是残缺版Vue,缺少模板解析器,故main.js中使用的了render函数

1
2
3
4
5
let vm = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

PS:为啥要使用残缺版Vue呢?完整版Vue比残缺版也就大个几百K?

因为例如以装修—铺瓷砖举个例子:

第一种:

​ 买瓷砖(Vue核心)+买工人(模板解析器)====》铺好的瓷砖+工人(引入完整Vue)

第二种:

​ 买瓷砖(Vue核心)+雇工人(模板解析器)====》铺好的瓷砖(使用render函数)

综上来看,使用render函数更容易避免资源浪费

总结

  1. 关于不同版本的Vue:
    1. vue.js是完整版的Vue。包含:核心功能+模板解析器
    2. vue.runtime.xxx.js是运行版的Vue。只包含:核心功能,没有模板解析器
  2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElment函数去指定具体内容

ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式
    • 打标识:

    • 获取:this.$refs.xxx

props配置项

数组型

1
props:["name","age","sex"]

**优点:**简单方便

**优点:**不能添加默认值

数组型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//接收的同时对数据类型进行限制
props:{
name:String,
sex:String,
age:Number
}

//完整型(类型限制+默认值的指定+必要性的限制)
props:{
name:{
type:String, //要传的name类型
required:true //是否必传
},
age:{
type:Number, //要传的age类型
default:99 //不传的话默认值是XX
},
sex:{
type:String, //要传的sex类型
required:true //是否必传
},
}

PS:

  1. 如果要想修改外部传进来的属性值,要在data中先定义一个属性来接收
  2. key,ref等官方已经声明的属性,不能作为传递的属性值去传递与接收

总结

功能:让组件接收外部传过来的数据

  1. 传递数据

    <Demo name="xxx"/>

  2. 接收数据:

    1. 只接收
    1
    props:['name']
    1. 限制类型
    1
    2
    3
    props:{
    name:string
    }
    1. 限制类型、限制必要性、指定默认值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    props:{
    name:{
    type:String, //要传的name类型
    required:true //是否必传
    },
    age:{
    type:Number, //要传的age类型
    default:99 //不传的话默认值是XX
    },
    }

备注:props是只读的,Vue底层会监测你对props的修改。如果进行了修改,就会发出警告,若业务确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

mixin混入

功能:可以把多个组件共用的配置项提取成一个混入对象

使用方式:

  1. 第一步定义混合,例如:{data(){…},methods:{…}}
1
2
3
4
5
6
7
8
9
10
export const minxin={
methods:{
showName(){
alert(this.name)
}
},
mounted(){
console.log("你好啊!")
}
}
  1. 使用混入,例如:
    1. 全局混入:Vue.mixin(xxx)
    2. 局部混入:minxins:[‘xxx’]

插件

功能:用于增强Vue

本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是参见使用者传递的数据

定义插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
对象.install=function(Vue,options){

//1.增加全局过滤器
Vue.filter(....)
//2.添加全局指令
Vue.directive(...)
//3.配置全局混入(合)
Vue.mixin(...)
//4.添加实例方法
Vue.prototype.$myMethod=function(){...}
Vue.prototype.$myProperty=xxxx
}

使用插件:==Vue.use(xxxxxxxx)==

浏览器本地存储

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过Window.sessionStorage和Window.localStorage属性来实现

  3. 相关API:

    1. xxxxxStorage.setItem

      该方法接受一个键和值作为参数,会把键值添加到存储中

    2. xxxxxStorage.getItem

      该方法接受一个键名作为参数,返回键名对应的值

    3. xxxxxStorage.removeItem

      该方法接受一个键名作为参数,并把该键名与值从存储中删除

    4. xxxxxStorage.clear

      该方法会清除存储中所有数据

  4. 备注:

    1. SessionStorage存储的内容会随着浏览器窗口关闭而关闭
    2. LocalStorage存储的内容,需要手动清除才会消失
    3. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null
    4. JSON.parse(null)的结果依然是null

localStorage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="setData()">点我存!</button>
<button onclick="getData()">给我读!!</button>
<button onclick="removeData()">给我删!!!</button>
<button onclick="clearData()">给我清空!!!!</button>
<script type="text/javascript">
let p={
name: '张三',
age:18
}
function setData(){
localStorage.setItem('msg','hello');
localStorage.setItem('msg2',666);
localStorage.setItem('person',JSON.stringify(p));

}
function getData(){
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
console.log(JSON.parse(localStorage.getItem('person')))
//没有该字段的情况下,读取出来的就是null
}

/* function removeData(){
localStorage.removeItem('msg');
}*/
removeData=()=>{
localStorage.removeItem('msg');
}
function clearData(){
localStorage.clear();
}
</script>
</body>

</html>


sessionStorage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="setData()">点我存!</button>
<button onclick="getData()">给我读!!</button>
<button onclick="removeData()">给我删!!!</button>
<button onclick="clearData()">给我清空!!!!</button>
<script type="text/javascript">
let p={
name: '张三',
age:18
}
function setData(){
sessionStorage.setItem('msg','hello');
sessionStorage.setItem('msg2',666);
sessionStorage.setItem('person',JSON.stringify(p));

}
function getData(){
console.log(sessionStorage.getItem('msg'))
console.log(sessionStorage.getItem('msg2'))
console.log(JSON.parse(sessionStorage.getItem('person')))
}

/* function removeData(){
sessionStorage.removeItem('msg');
}*/
removeData=()=>{
sessionStorage.removeItem('msg');
}
function clearData(){
sessionStorage.clear();
}
</script>
</body>

</html>

自定义事件

如果存在子组件School与子组件Student,当点击按钮后,如何将对应子组件的某个属性显示到父组件中?

image-20211223190217146

链接:https://pan.baidu.com/s/15Mbtq94coWd-QAccgDKUeg
提取码:6e19

PS:子给父传递数据常用的有两种方法,一个是通过$emit, 另一个则是使用props配置项

绑定

image-20211223192633801

$emit

第一种方法

image-20211223192746970

image-20211223192833147

绑定自定义事件时,只要$emit绑定的父组件方法无误,即可将参数传递过去

第二种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
<div class="home">
<h1>你好啊,{{ schoolName }}</h1>
<School :get-school-name="getSchoolName"></School>
<!-- <Student @chrishly="demo"></Student>-->
<!--升级方法-->
<Student ref="student"></Student>
</div>
</template>

<script>
// @ is an alias to /src
/* import HelloWorld from '@/components/HelloWorld.vue' */
import School from './components/School'
import Student from './components/Student'

export default {
name: 'App',
components: {
/* HelloWorld, */
School,
Student
},
data () {
return {
msg: '你好啊',
schoolName: ''
}
},
methods: {
getSchoolName (name) {
console.log('学校的名称:' + name)
this.schoolName = name
},
demo (name) {
console.log('demo被调用了')
this.schoolName = name
}
},
mounted () {
console.log(this)
this.$refs.student.$on('chrishly', this.demo)
}
}
</script>
<style scoped>
.home {
background-color: #e0e0e0;
}
</style>

image-20211228161757632

image-20211228161842503

但是要注意【vue3中去除了$on,$off 和 $once 实例方法】,因此在vue3中使用会报以下错误

image-20211228162051651

==555,找这错误找了好久……T^T==

props配置项

image-20211223201254508

image-20211223201415819

使用props配置项时,需要父组件给子组件预留一个位置【属性/方法】,然后在子组件中props接收后,通过这个方法去传递

解绑

在哪个组件中绑定的自定义事件,就在那个组件中进行解绑

  • 解绑一个自定义事件

    1
    2
    3
    unbind(){
    this.$off('chrishly')
    }
  • 解绑多个自定义事件

    1
    2
    3
    unbind(){
    this.$off(['chrishly','chrishly2'])
    }
  • 解绑所有的自定义事件

    1
    2
    3
    unbind(){
    this.$off()
    }

销毁

  • 销毁vc组件(如student组件)的的实例,销毁后所有student组件实例的自定义事件所有自定义事件都不奏效
  • 销毁vm,则子组件所有自定义事件都被销毁(但不包括自定义事件)

自定义事件的注意点

  1. 要注意this的指向【特别是回调函数的】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    //请问此时的this指向是不是问题
    mounted () {
    console.log(this)
    this.$refs.student.$on('chrishly', function(name){
    this.schoolname=name;
    })
    }
    //答案是肯定的,this的指向是student组件的实例对象,要怎样改才是正确的呢?
    //答案如下
    mounted () {
    console.log(this)
    this.$refs.student.$on('chrishly', name=>{
    this.schoolname=name;
    })
    }
    //或者如下
    mounted () {
    console.log(this)
    this.$refs.student.$on('chrishly', this.demo(name))
    }
    methods:{
    demo(name){
    this.schoolname=name;
    }
    }

    vc组件实例对象绑定自定义方法时,回调函数中的this指向的时vc组件实例对象,而不是vm,为啥改为箭头函数后就好了呢?因为箭头函数没有自己的this,于是它往上翻去找this,找到的是mounted中的,vue给了一个承诺,就是你标准的在vue中定义生命周期函数,生命周期函数中的this指向的就是vm。同理第二种改法也是因为methods中普通函数的指向也是vm

  2. 组件也可以绑定原生DOM事件,需要使用native修饰符

总结

  1. 一种组件间通信的方式,使用于**子组件===》父组件**
  2. 使用场景:==A==是==父组件==,==B==是==子组件==,B想给A传数据,那么就需要在A中给B绑定自定义事件【**事件的回调在A中**】
  3. 绑定与解绑详见上面两小节
  4. 使用this.$refs.xxx.$on(自定义事件, 回调)绑定自定义事件是回调**要么配置在methods中,要么使用箭头函数**,否则this的指向会有问题

全局事件总线(GlobalEventBus)

**全局事件总线:**任意组件间通信

image-20211229130436524

  1. 一种组件间通信的方式,适用于**任意组件间通信。**

  2. 安装全局事件总线【main.js】

    1
    2
    3
    4
    5
    6
    7
    new Vue({
    ...
    beforeCreate() {
    Vue.prototype.$bus=this;
    }
    ...
    }).$mount('#app')
  3. 使用事件总线

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的**回调在A组件本身**

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      <template>
      <div class="school">
      <h2>学校名称:{{ name }}</h2>
      <h2>学校地址:{{ address }}</h2>
      </div>

      </template>

      <script>
      export default {
      name: 'Student',
      props: ['getSchoolName'],
      data () {
      return {
      name: '嘀嘀嘀大学',
      address: '北京'
      }
      },
      methods: {
      },
      mounted(){
      this.$bus.$on('hello',(data)=>{
      console.log("我是School组件,我接收到了数据",data)
      })
      },
      beforeDestroy() {
      this.$bus.$off('hello')
      }
      }
      </script>
      <style scoped>
      .school {
      background-color: skyblue;
      }
      </style>

    2. 提供数据this.$bus.$emit("xxx",数据)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <template>
      <div class="student">
      <h2>学生名称:{{ name }}</h2>
      <h2>学生性别:{{ sex }}</h2>
      <button @click="sendStudentName">把学生名给School组件</button>
      </div>
      </template>

      <script>
      export default {
      name: 'Student',
      data () {
      return {
      name: '张三',
      sex: '男'
      }
      },
      methods: {
      sendStudentName(){
      this.$bus.$emit('hello',this.name)
      }
      },

      }
      </script>

      <style scoped>
      .student {
      background-color: pink;
      }
      </style>

消息订阅与发布

  1. 类似于**报纸订阅**

    报纸订阅:

    1. 订阅报纸:地址
    2. 邮递员送报纸:报纸

    消息订阅:

    1. 订阅消息:消息名(手机号,邮箱号)
    2. 发布消息:消息的内容
  2. 借助的第三方库:PubSub等

  3. 使用步骤:

    1. 安装第三方库,以PubSub为例

      1
      npm i pubsub-js
    2. 引入第三方库

      1
      import pubsub from 'pubsub-js'
    3. 使用第三方库

      1. 订阅消息【注意回调函数中的this指向不是vm】

        1
        pubsub.subscribe(消息名,回调函数【消息名,数据】)
      2. 发布消息

        1
        this.pubId=pubsub.publish(消息名,数据)
      3. 取消订阅

        1
        pubsub.unsubscribe(this.pubId)
  4. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pid)去**取消订阅**

Vue动画

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

  2. 图示:

    image-20220104131933012

  3. 写法:

    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>包裹要过度的元素,并配置name属性

      1
      2
      3
      4
      5
      <transition name="hello">
      <h1 v-show="isShow">
      你好啊!
      </h1>
      </transition>
    3. 备注:若有多个元素需要过去。则需要使用<transition-group>,且每个元素都要指定key

插槽

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

默认插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
父组件中:
<Category>
<div>
html结构1
</div>
</Category>
子组件中:
<template>
<div>
<!--定义插槽-->
<slot>插槽默认内容</slot>
</div>
</template>

具名插槽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
父组件中:
<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>

作用域插槽

  1. 理解:**数据在组件的自身,但根据数据生成的结果需要组件的使用者来决定**。(game数据在Category组件中,但使用数据所遍历出来的结构由App组件来决定)

  2. 具体编码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    父组件中:
    <Category>
    <template scope="scopeData">
    <!--生成的是ul列表-->
    <ul>
    <li v-for="(item,index) in scopeData.games" :key="index">{{item}}</li>
    </ul>
    </template>
    </Category>
    <Category>
    <template slot-scope="scopeData">
    <!--生成的是h4标题-->
    <h4 v-for="(item,index) in scopeData.games" :key="index">
    {{item}}
    </h4>
    </template>
    </Category>
    子组件中:
    <template>
    <div>
    <!--定义插槽-->
    <slot :games="games">插槽默认内容</slot>
    </div>
    </template>
    <script>
    export default {
    name: "Category",
    data(){
    return {
    games: ['GTA1', 'GTA2', 'GTA3', 'GTA4']
    }
    }
    }
    </script>

Vuex

理解Vuex

  1. Vuex是什么?

    1. 概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间的通信的方式,且适用于任意组件间通信【教室里一个教师给很多学生讲课】
    2. Github地址:https://github.com/vuejs/vuex
  2. 什么时候使用Vuex

    1. 多个组件依赖于同一状态
    2. 来自不同组件的行为需要变更同一状态
  3. Vuex原理图

    image-20220104163344430

    VueComponets:顾客

    Actions:服务员

    Mutations:厨师

    State:菜品

    VueComponets和服务员说他想要一份蛋炒饭,服务员和厨师说有位顾客需要一份蛋炒饭,厨师加工菜品后,将菜品给了顾客VueComponets【在已知需要处理的数据时,其实是可以越过Actions,直接与Mutations交流的,Actions的作用主要是预防有未知数据需要向后端发送请求时,承担起向后端发送请求,获取数据的一个桥梁作用】

    图中缺少了一个管理者storestore统筹管理State、Action、Mutations

搭建Vuex环境

搭建步骤:

  1. npm i vuex
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    2. 创建文件:`src/store/index.js`

    ```js
    //该文件用于创建vuex中最核心的store
    import Vue from "vue";
    //引入vuex
    import Vuex from 'vuex';
    //引用vuex插件
    Vue.use(Vuex)
    //准备actions,用于相应组件中的动作
    const actions={}
    //准备mutations,用于操作数据(state)
    const mutations={}
    //准备state,用于存储数据
    const state={}

    //创建store并暴露store
    export default new Vuex.Store({
    actions,
    mutations,
    state
    })
  2. main.js中创建vm时传入store配置项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import Vue from 'vue'
    import App from './App.vue'
    import store from '../store'

    Vue.config.productionTip = false

    /*const Demo=Vue.extend({});
    const d=new Demo();

    Vue.prototype.x=d;*/

    new Vue({
    render: h => h(App),
    store
    }).$mount('#app')

求和案例

image-20220105094801250

count.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template>
<div>
<h1>当前求和为:{{ $store.state.sum }}</h1>

<select v-model.number="n">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="incrementOdd">当前求和为奇数再加</button>
<button @click="incrementWait">等一等再加</button>
</div>
</template>

<script>
export default {
name: "Count",
data() {
return {
n: 1 //用户选择的数字
}
},
methods: {
increment() {
//无业务逻辑,可以直接越过action,去找mutation请求
this.$store.commit("ADD", this.n)
},
decrement() {
this.$store.commit('DELETE', this.n)
},
incrementOdd() {
this.$store.dispatch("addOdd", this.n)
},
incrementWait() {
this.$store.dispatch("addWait", this.n)

}

}
}
</script>

<style scoped>

</style>

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//该文件用于创建vuex中最核心的store
import Vue from "vue";
//引入vuex
import Vuex from 'vuex';
//引用vuex插件
Vue.use(Vuex)
//准备actions,用于相应组件中的动作【一般业务逻辑写在这里】
const actions = {
add(context, value) {
console.log("action中的add被引用了", context, value)
context.commit('ADD', value)
},
delete(context,value){
console.log("action中的delete被引用了", context, value)
context.commit('DELETE', value)

},
addOdd(context,value){
if (context.state.sum % 2) {
context.commit("ADDODD", value)
}
},
addWait(context,value){
setTimeout(() => {
context.commit("ADD", value)
}, 500)
}
}
//准备mutations,用于操作数据(state)
const mutations = {
ADD(state, value) {
console.log("mutations中的ADD被引用了", state, value)
state.sum += value
},
DELETE(state, value) {
console.log("mutations中的ADD被引用了", state, value)
state.sum -= value
},
ADDODD(state, value) {
console.log("mutations中的ADD被引用了", state, value)
state.sum += value
},
}
//准备state,用于存储数据
const state = {
sum: 0
}

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

getters配置项

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工

  2. index.js中追加getters配置【在index.js中添加,类似于计算属性】

    1
    2
    3
    4
    5
    const getters ={
    xxx(state){
    return ...
    }
    }
  3. 组件中读取数据:$store.getters.xxx

四个map方法的使用

  1. mapState方法:用于映射state中的属性为计算属性

    1
    2
    3
    4
    5
    6
    7
    8
    import {mapState} from 'vuex'
    ...
    computed:{
    //借助mapState生成计算属性(对象写法)
    ...mapState({xxx:'xxx',xxxx:'xxxx'})
    //借助mapState生成计算属性(数组写法)【需要映射名与index.js中state保持一致】
    ...mapState(['xxx','xxxx'])
    }
  2. mapGetters方法:用于映射getters中的属性为计算属性

    1
    2
    3
    4
    5
    6
    7
    8
    import {mapGetters} from 'vuex'
    ...
    computed:{
    //借助mapGetters生成计算属性(对象写法)
    ...mapGetters({xxx:'xxx',xxxx:'xxxx'})
    //借助mapGetters生成计算属性(数组写法)【需要映射名与index.js中getters中保持一致】
    ...mapGetters(['xxx','xxxx'])
    }
  3. mapActions方法:用于帮助生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <button @click="increment(params)">+</button>
    </template>
    <script>
    import {mapActions} from 'vuex'
    ...
    methods:{
    //借助mapActions生成(对象写法)
    ...mapActions({increment:'xxx',xxxx:'xxxx'})
    //借助mapActions生成(数组写法)【需要映射名与index.js中actions中保持一致】
    ...mapActions(['xxx','xxxx'])
    }
    </script>

  4. mapMutations方法:用于帮助生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <button @click="increment(params)">+</button>
    </template>
    <script>
    import {mapMutations} from 'vuex'
    ...
    methods:{
    //借助mapMutations生成(对象写法)
    ...mapMutations({increment:'xxx',xxxx:'xxxx'})
    //借助mapMutations生成(数组写法)【需要映射名与index.js中mutations中保持一致】
    ...mapMutations(['xxx','xxxx'])
    }
    </script>

模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确

  2. 修改index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const countAbout = {
    namespaced:true, //开启命名空间
    state:{
    x:1
    },
    mutations:{...},
    actions:{...},
    getters:{
    xxx(state){
    return ...
    }
    }

    }
    const personAbout = {
    namespaced:true, //开启命名空间
    state:{...},
    mutations:{...},
    actions:{...},
    }
    const store = new Vuex.Store({
    modules:{
    contentAbout,
    personAbout
    }
    })
  3. 开启命名空间后,组件中读取state数据:

    1
    2
    3
    4
    //方法一:自己直接读取
    this.$store.state.personAbout.list
    //方法二:借助mapState读取
    ...mapState('countAbout',['xxx','xxxx'])
  4. 开启命名空间后,组件中读取getters数据:

    1
    2
    3
    4
    //方法一:自己直接读取
    this.$store.getters('personAbout/firstPersonName')
    //方法二:借助mapGetters读取
    ...mapGetters('countAbout',['xxx'])
  5. 开启命名空间后,组件中调用dispatch

    1
    2
    3
    4
    //方法一:自己直接读取
    this.$store.dispatch('personAbout/addPerson',person)
    //方法二:借助mapActions读取
    ...mapActions('countAbout'{increment:'xxx',xxxx:'xxxx'})
  6. 开启命名空间后,组件中调用commit

    1
    2
    3
    4
    //方法一:自己直接读取
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方法二:借助mapActions读取
    ...mapActions('countAbout'{increment:'xxx',xxxx:'xxxx'})

路由

理解路由

  1. 理解:一个路由(route)就是一组映射关系(key,value),多个路由需要路由器(route)进行管理。
  2. 前端路由:key是路径,value是组件
  3. 几个注意点:
    1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
    2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
    3. 每个组件都有自己的$route属性,里面存储着自己的路由信息
    4. 整个应用只有一个router,可以通过组件的$router属性获取到

路由的基本使用

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入组件
    import About from '../components/About'
    import Home from '../components/Home'

    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    router:[
    {
    path:'/about',
    component:About
    }
    {
    path:'/home',
    component:Home
    }
    ]
    })
    export default router
  4. 实现切换(active-class可配置高亮样式)

    1
    <router-link active-class="active" to="/about">About</router-link>
  5. 指定展示位置

    1
    <router-view></router-view>

嵌套(多级)路由

  1. 配置路由规则,使用children配置项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入组件
    import About from '../components/About'
    import Home from '../components/Home'
    import News from '../components/News'
    import Messages from '../components/Messages'

    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    router:[
    {
    path:'/about',
    component:About
    }
    {
    path:'/home',
    component:Home,
    children:[
    {
    path:'news',
    component:News
    },
    {
    path:'messages',
    component:Messages
    }
    ]
    }
    ]
    })
    export default router
  2. 跳转(要写完整链接)

    1
    <router-link active-class="active" to="/home/news">About</router-link>

路由传参

query参数

  1. 传递参数

    1. 字符串写法

      1
      2
      3
      4
      5
      6
      <ul>
      <li v-for="item in messageList">
      <router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">{{item.title}}</router-link>
      </li>
      </ul>

    2. 对象写法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <ul>
      <li v-for="item in messageList">
      <router-link :to="{
      path:'/home/message/detail',
      query:{
      id:item.id,
      title:item.title
      }
      }">
      {{item.title}}
      </router-link>
      </li>
      </ul>
  2. 接收参数

    1
    2
    $route.query.id
    $route.query.title

params参数

  1. 配置路由,生命接收params参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    path:'/home',
    component:Home,
    children:[
    {
    path:'news',
    component:News
    },
    {
    path:'messages',
    component:Messages,
    children:[{
    name:"getDetail"
    path:'detail/:id/:title',
    component:Details
    }]
    }
    ]
    }
  2. 传递参数

    1. 字符串写法

      1
      <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    2. 对象写法

      1
      2
      3
      4
      5
      6
      7
      <router-link :to="{
      name:"getDetail"
      params:{
      id:666,
      title:"你好"
      }
      }">跳转</router-link>
  3. **特别注意:**路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置

  4. 接收参数

    1
    2
    $route.params.id
    $route.params.title

命名路由

  1. 可以简化路由的跳转

  2. 如何使用

    1. 给路由命名:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
            
      {
      path:'/home',
      component:Home,
      children:[
      {
      name:'haha'
      path:'news',
      component:News
      }
      ]
      }
    2. 简化跳转

      1
      2
      3
      4
      <!--简化前,需要写完整路径-->
      <router-link to="/home/news">跳转</router-link>
      <!--简化前后,直接通过名字跳转-->
      <router-link :to="{name:"haha"}">跳转</router-link>

路由的props配置项

  1. 谁接收数据,就在谁里面绑定props配置

  2. 作用:让路由组件更方便的收到参数

  3. 举例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    name:"getDetail"
    path:'detail/:id/:title',
    component:Details,
    //第一种写法,props为对象,该对象中所有的key-value的组合最后都会通过props传给Detail组件
    //props:{a:900}
    //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
    //props:true
    //第三种写法,props时函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
    props(route){
    return {
    id:route.query.id,
    title:route.query.title
    }
    }



    }

route-link的replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别是pushreplacepush是追加历史记录,replace是替换当前记录,路由跳转时候默认是push
  3. 如何开启replace模式:<router-link replace><router-link>

编程式路由导航

  1. 作用:不借助<router-link>实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    this.$router.push({
    name:'getDetail',
    params:{
    id:xxx,
    title
    }
    })

    this.$router.replace({
    name:'getDetail',
    params:{
    id:xxx,
    title:xxx
    }
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go(number) //可前进也可后退

缓存路由组件

  1. 作用:让不展示的路由由组件保持挂载,不被销毁

  2. 具体编码:

    1
    2
    3
    4
    <!--组件名-->
    <keep-alive include="News">
    <router-view></router-view>
    </keep-alive>

两个新的生命周期钩子【路由独有】

activeddeactived

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态
  2. 具体名字:
    1. actived路由组件被激活时触发
    2. deactived路由组件失活时触发

路由守卫

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

全局守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//全局前置守卫【初始化执行,每次路由切换前执行】
router.beforeEach((to,from,next)=>{
if(to.meta.isAuth){//标志判断当前路由是否需要鉴权
if(localStoge.getItem('school'==='haha')){//权限控制具体规则
next() //执行
}else{
alert("暂无权限查看")
}

}
})
//全局后置守卫【初始化时执行,每次路由切换后执行】
router.afterEach((to,from)=>{
if(to.meta.title){
document.title=to.meta.title //修改网页的title

}else{
document.title='vue_test'
}
})

独享守卫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
name:'xinwen',
path:'news',
component:News,
meta:{
isAuth:true,
title:'新闻'
},
beforeEnter:(to,from,next)=>{
if(to.meta.isAuth){//标志判断当前路由是否需要鉴权
if(localStoge.getItem('school'==='haha')){//权限控制具体规则
next() //执行
}else{
alert("暂无权限查看")
}

}
}
}

组件内守卫

1
2
3
4
//进入守卫,通过路由规则,进入组件时被调用
beforeRouterEnter(to,from,next){},
//离开守卫,通过路由规则,离开组件时被调用
beforeRouterLeave(to,from,next){},

history模式与hash模式

  1. 对于一个url来说,什么是hash值? ===》#及其后面的内容就是hash值
  2. hash值不会包含在HTTP请求中,即:hash值不会带给服务器
  3. hash模式
    1. 地址中永远带着#号,不美观
    2. 若以后将地址通过第三方手机app分享,若app校验严重,则地址会被标记为不合法
    3. 兼容性较好
  4. history模式
    1. 地址干净美观
    2. 兼容行和hash模式相比较差
    3. 应用部署上线时需要后端人员支持,解决刷新页面服务器404的问题

Vue3

常用Composition API

setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数
  2. set是所有**Composition API(组合API)**”表演的舞台“。
  3. 组件中所用到的:数据、方法等等,均要配置在setup中
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用。(重点关注)
    2. 若返回一个渲染函数,则可以自定义渲染内容(了解)
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用
      • Vue2.x配置(data、methods、computed…)中**可以访问到**setup中的属性、方法
      • 但在setup中**不能访问到**Vue2.x配置(data、methods、computed…)
      • 如果有重名,setup优先
    2. setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性

ref函数

  • 作用:定义一个响应式的数据
  • 语法:const xxx = ref(initValue)
    • 创建一个包含响应式数据的**引用对象(reference对象,简称ref对象)**
    • JS中操作数据:xxx.value
    • 模板中读取数据:不需要.value,直接<div>{{xxx}}</div>
  • 备注:
    • 接收的数据可以是:基本类型,也可以是对象类型
    • 基本数据类型的数据:响应式依然是靠Object.defineProperty()getset完成的
    • 对象类型的数据:内部求助了Vue3中的一个新函数—-reactive函数

reactive函数

  • 作用:定义一个**对象类型**的响应式数据(基本类型别用它,用ref函数
  • 语法:const 代理对象=reactive(被代理对象)接收一个对象(或数组),返回一个**代理器对象(proxy的实例对象,简称proxy对象)**
  • reactive定义的响应式数据是”深层次的“
  • 内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据都是响应式的

reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:**基本类型数据**
    • reactive用来定义:**对象(或数组)类型数据**
    • 备注:ref也可以用来定义**对象(或数组)类型数据,它内部会自动通过reactive转为代理对象**
  • 从原理角度对比
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)
    • reactive通过使用**proxy来实现响应式(数据劫持),并通过Reflect操作源对象**内部的数据
  • 从使用角度来看:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取**不需要**.value
    • reactive定义的数据:操作数据与读取数据,**均不需要**.value

setup的两个注意点

  • setup执行的时机
    • 在beforeCreate之前执行一次,this是undefined
  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了属性
    • context:上下文对象
      • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
      • slots:收到的插槽内容,相当于this.$slots
      • emit:分发自定义事件的函数,相当于this.$emit

自定义hook函数

  • 什么是hook?
    • 本质是一个函数,把setup函数中使用的Composition API进行了封装
  • 类似于vue2.x中的mixin
  • 自定义hook的优势:复用代码,让setup中逻辑更清楚易懂

toRef

  • 作用:创建一个ref对象,其value值指向另一个对象中的某个属性值
  • 语法:const name=toRef(person,'name')
  • 应用:要将响应式对象中的某个属性单独提供给外部使用时
  • 扩展:toRefstoRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person)

Vue的响应式原理

Vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty对属性的读取、修改进行拦截(数据劫持)

    • 数组类型:通过重写更新数据的一系列方法来实现拦截。(对数组的更新方法进行了包裹)

      1
      2
      3
      4
      Object.difineProperty(data,'count',{
      get(){},
      set(){}
      })
  • 存在问题:

    • 新增属性,删除属性,界面不会更新
    • 直接通过下标修改数据,界面不会自动更新

Vue3.0的响应式

  • 实现原理:

    • 通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射):对被代理对象的属性进行操作

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      new Proxy(data,{
      //拦截读取属性值
      get(target,prop) {
      return Reflect.get(target,prop)
      },
      //拦截设置属性值或添加新属性
      set(target,prop,value){
      return Reflect.set(target,prop,value)
      },
      //拦截删除属性
      deleteProperty(target,prop) {
      return Reflect.deleteProperty(target,prop)
      }
      })

计算属性与监视

计算属性(computed)

  • 与Vue2.x中computed的配置功能一致

  • 写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { computed } from 'vue'
    setup() {
    ...
    //计算属性-简写
    let fullName = computed(()=>{
    return xxx
    })
    //计算属性完整
    let fullName =computed({
    get(){
    return xxx
    },
    set(Value){
    const nameArr = value.split("-")
    person.firstName=nameArr[0]
    person.firstName=nameArr[1]
    }
    })
    }

监视(watch)

  • 与Vue2.x中watch配置功能一致
  • 两个小“坑”:
    • 监视reactive定义的响应式数据时:oldValue无法正确获取,强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { watch } from 'vue'
setup() {
...
let sum = ref(0)
let msg = ref('你好')
let person = reactive({
name:'张三',
age:18,
job:{
salary:2
}
})
//情况一:监视ref所定义的一个响应式数据
watch(sum,(newValue,oldValue)=>{
console.log(newValue,oldValue)
},{immediate:true})
//情况二:监视ref所定义的多个响应式数据
watch([sum,msg],(newValue,oldValue)=>{
console.log(newValue,oldValue)
})
//情况三:监视reactive所定义的一个响应式数据,注意:此处无法正确获取oldValue
watch(person,(newValue,oldValue)=>{
console.log(newValue,oldValue)
})

//情况四:监视reactive所定义的响应式对象数据的某个属性
watch(person.name,(newValue,oldValue)=>{
console.log(newValue,oldValue)
})

//情况五:监视reactive所定义的响应式对象数据的某些属性
watch(()=>person.name=>person.age,(newValue,oldValue)=>{
console.log(newValue,oldValue)
})

//特殊情况【操作salary,多层次数据,需要使用deep:true】


return{
sum,
msg,
person
}
}

watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调
  • watchEffect的的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
  • watchEffect有点像computed:
    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用些返回值
1
2
3
4
5
//watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调
watchEffect(()=>{
const x1=sum.value
const x2=person.age
})

生命周期

  • Vue3.0可以继续使用Vue2.x中的生命周期钩子,但是有两个被更名:
    • beforeDestroy改名为beforeUnmount
    • destroyed改名为unmounted
  • Vue3.0也提供了Compostion API形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    • beforeCreate===>setup
    • created===>setup
    • beforeMount===>onBeforeMount
    • mounted===>onMounted
    • beforeUpdate===>onBeforeUpdate
    • updated===>onUpdated
    • beforeUnmount===>onBeforeUnmount
    • unmounted===>onUnmounted

官方APi

$nextTick

$nextTick指定的回调函数会在DOM节点更新之后,再去加载

语法

1
this.$nextTick(回调函数)

作用

在下一次更新结束后执行其指定的回调

什么时候用?

当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行【例如input隐藏了,后面需要显示时需要聚焦】