基本概念

MVVM设计模式

在MVVM设计模式中由3个部分组成:

  • M:Model,数据模型(保存数据,处理数据业务逻辑)
  • V:View,视图(展示数据,与用户交互)
  • VM:View Model,数据模型和视图的桥梁(M是中国人,V是美国人,VM就是翻译)

MVVM设计模式最大的特点就是支持数据的双向传递。数据可以从M => VM => V ;也可以从 V => VM =>M

Vue就是基于MVVM设计模式的

  • 被控制的区域:View
  • Vue实例对象:View Model
  • 实例对象中的data:Model
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 这里就是MVVM中的View -->
<div id="app">
<p v-once>姓名:{{ name }}</p>
</div>

<script>
//这里就是MVVM中的 View Model
const vue = new Vue({
el: '#app',
//这里就是MVVM中的 Model
data: {
name: "zs",
msg: "百度"
}
});
</script>

数据双向传递

默认情况下,Vue只支持数据单项传递M => VM => V。

但是由于Vue是基于MVVM设计模式的,所以也提供了双向传递的能力。

<input> <textarea> <select> 元素上可以用 v-model 指令创建双向数据绑定。

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<input type="text" v-model="msg">
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "zs",
msg: "百度"
}
});
</script>

Vue配置项

开始使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 1.导入VUE -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>

<div id="app">{{ name }}</div>

<script>
//2.创建一个vue实例对象
const vue = new Vue({
//3.告诉Vue的实例对象,将来需要控制界面上的哪个区域
el: '#app',
//4.告诉Vue的实例对象,被控制区域的数据是什么
data: {
name: "zs"
}
});
</script>

methods 定义函数

用于存储监听事件回调函数

详见 指令 => v-on

directives 定义指令

用于定义局部指令

详见 指令 => 自定义指令

components 定义组件

用于定义局部组件

详见 组件 => 局部组件

watch

监听数据变化、监听路由变化

详见 Vue Router => 监听路由变化

render 渲染方式

Vue渲染组件的两种方式:

  • 1.先定义注册组件,然后在Vue实例中当作标签来使用
  • 2.先定义注册组件,然后通过Vue实例的render方法来渲染

两种渲染的区别:

  • 1.当作标签来渲染,不会覆盖Vue实例控制区域
  • 2.通过render方法来渲染,会覆盖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
<div id="app">
</div>

<template id="one">
<div>
<h1>我是组件6666</h1>
</div>
</template>

<script>
Vue.component("xyz", {
template: "#one"
});
const vue = new Vue({
el: '#app',
// 接收一个参数,这个参数是函数
render: function (createElement) {
// createElement();
// 给它传一个参数,根据模板渲染哪个组件
// return 模板就会把这个模板渲染到界面上
return createElement("xyz");
},
data: {},
methods: {}
});
</script>

computed 计算属性

专门用于定义计算属性

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
<div id="app">
<!--
注意点:
虽然在定义计算属性的时候是通过一个函数返回的数据
但是在使用计算属性的时候不能在计算属性名称后面加上()
因为它是一个属性而不是一个函数(方法)
-->
<p>{{msg}}</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {},
methods: {}, //专门用于存储监听事件回调函数
directives: {}, //专门用于定义局部指令的
//专门用于定义计算属性的
computed: {
msg: function () {
let res = "abcdef".split("").reverse().join("");
return res;
}
}
});
</script>

计算属性computed 和 函数methods 的区别

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
<div id="app">
<!-- msg1()将在控制台打印三次-->
<p>{{msg1()}}</p>
<p>{{msg1()}}</p>
<p>{{msg1()}}</p>
<!-- msg2将在控制台打印一次-->
<p>{{msg2}}</p>
<p>{{msg2}}</p>
<p>{{msg2}}</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {},
//函数的特点:每次调用都会执行
methods: {
msg1() {
console.log("函数msg1()被执行了");
let res = "abcdef".split("").reverse().join("");
return res;
}
},
directives: {},
//计算属性的特点:只要返回的结果没有发生变化,那么计算属性就只会被执行一次
//应用场景:由于计算属性会将返回的结果缓存起来,所以返回的数据不经常发生变化,那么使用计算属性的性能会比使用函数的性能高
computed: {
msg2: function () {
console.log("计算属性msg2被执行了");
let res = "abcdef".split("").reverse().join("");
return res;
}
}
});
</script>

filters 过滤器

过滤器和函数和计算属性一样都是用来处理数据的。但是过滤器一般用于格式化插入的文本数据

  • 只能在插值语法和 v-bind 中使用

自定义全局过滤器

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
<div id="app">
<p>{{name | chulidaxue}}</p>
<p>{{name | chulidaxue | chulibaidu}}</p>
</div>

<script>
/*
第一个参数:过滤器名称
第二个参数:处理数据的函数
注意点:默认情况下处理数据的函数接收一个参数,就是当前要被处理的数据
*/
Vue.filter("chulidaxue", function (value) {
value = value.replace(/大学/g, "学院");
return value;
});
Vue.filter("chulibaidu", function (value) {
value = value.replace(/百度/g, "淘宝");
return value;
});
const vue = new Vue({
el: '#app',
data: {
name: "百度大学,JS大学,前端大学"
}
});
</script>

自定义局部过滤器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<p>{{name | chulidaxue}}</p>
<p>{{name | chulidaxue | chulibaidu}}</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "百度大学,JS大学,前端大学"
},
//专门用于定义局部过滤器的
filters: {
"chulidaxue": function (value) {
value = value.replace(/大学/g, "学院");
return value;
},
"chulibaidu": function (value) {
value = value.replace(/百度/g, "淘宝");
return value;
}
}
});
</script>

案例:利用过滤器对时间进行格式化

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
<div id="app">
<p>{{time | dateFormart("yyyy-MM-dd")}}</p>
<p>{{time | dateFormart()}}</p>
</div>

<script>
/*
注意点: 在使用过滤器的时候, 可以在过滤器名称后面加上()
如果给过滤器的名称后面加上了(), 那么就可以给过滤器的函数传递参数
*/
Vue.filter("dateFormart", function (value, fmStr) {
// console.log(fmStr);
let date = new Date(value);
let year = date.getFullYear();
let month = date.getMonth() + 1 + ""; // + "" 转换为字符串
let day = date.getDate() + "";
let hour = date.getHours() + "";
let minute = date.getMinutes() + "";
let second = date.getSeconds() + "";
//字符串.padStart(2, "0") 第一个参数表示判断的长度,第二个参数表示拼接的字符串
if (fmStr && fmStr === "yyyy-MM-dd") {
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")}`;
}
return `${year}-${month.padStart(2, "0")}-${day.padStart(2, "0")} ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}:${second.padStart(2, "0")}`;
});
const vue = new Vue({
el: '#app',
data: {
time: Date.now()
},
});
</script>

生命周期方法

和wbpack生命周期方法一样,都是从生到死的特定阶段调用的方法。生命周期钩子=生命周期函数=生命周期事件

创建期间的生命周期方法:

  • beforeCreate:仅仅表示Vue实例刚被创建出来,还没有初始化好实例中的数据和方法,所以此时不能访问实例中的data和methods
  • created:能够访问实例中保存的data和methods
  • beforeMount:Vue已经编译好了最终模板,但是还没有渲染到界面上
  • mounted:完成了模板的渲染,可以拿到界面上渲染之后的内容了

运行期间的生命周期方法:

  • beforeUpdate: 调用beforeUpdate表示Vue实例中保存的数据被修改了(未修改不会调用); 此时界面的数据还没有更新
  • updated:数据被修改并且界面也同步了修改的数据。即:数据和界面都同步更新之后就会调用 updated

销毁期间的生命周期方法:

  • beforeDestroy:表示当前即将被销毁(未销毁不会调用);依然能访问data和methods
  • destroyed:表示当前组件已经被销毁。不要在这个生命周期方法中再去操作组件中的数据和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<input v-model="num">
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
num: 0
},
mounted:function(){
//....
},
updated: function () {
console.log(this.num);
},
});
</script>

指令

指令就是Vue内部提供的一些自定义属性,这些属性中封装好了Vue内部实现的一些功能,只要使用这些指令就可以使用Vue中实现的这些功能。通过这些指令可以让 Vue 对一个 DOM元素进行各种骚操作,如 v-oncev-model 等指令。

Vue文档-指令

v-text

1
2
3
<span v-text="msg">这里有文字也不会显示,只实现msg</span>
<!-- 等同于 -->
<span>{{msg}}</span>

v-html

在你的站点上动态渲染任意的 HTML 是非常危险的,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要将用户提供的内容作为插值

1
2
3
4
5
6
7
8
9
10
11
12
13
<div id="app">
<p v-html="msg">这里写什么都不显示</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "zs",
msg: "<div>我是div</div>"
}
});
</script>

v-if

v-if v-else-if v-else 的元素必须紧跟在一起,否则不能识别。

  • 如果取值为true,就渲染元素
  • 取值为false就不会渲染元素(重点注意)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<p v-if="name">写什么就显示什么</p>
<p v-if>写什么就显示什么</p>
<p v-if>{{name}}</p>

<!-- 不及格 -->
<p v-if=" fenshu>85 ">优秀</p>
<p v-else-if=" fenshu>60 ">良好</p>
<p v-else>不及格</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "zs",
fenshu: 59
}
});
</script>

v-show

v-show 和 v-if 一样,都是用来条件渲染的,取值为true就显示;取值为false就不显示。

  • v-show 取值为false也会创建元素,只是给该元素加了 display:none
  • v-if 取值为false不会创建该元素
  • 频繁切换/显示时,v-if频繁创建/删除元素更消耗性能。所以企业开发中需要频繁切换显示元素,就是用v-show,否则使用v-if
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<p v-show>错误写法,但不报错,只是不显示</p>
<p v-show="name">这里写什么就显示什么</p>

<p v-show=" fenshu>85 ">优秀</p>
<p v-show=" fenshu>=60 ">良好</p>
<p v-show=" fenshu<60 ">不及格</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "zs",
fenshu: 60
}
});
</script>

v-for

相当于js中的 for in 循环,可以根据数据多次渲染元素

  • 可以遍历数组、字符、数字、对象
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
<!--
▮索引0对应值是张三▮索引1对应值是李四▮索引2对应值是王五▮索引3对应值是麻子
▮索引0对应值是字▮索引1对应值是符▮索引2对应值是串▮索引3对应值是a▮索引4对应值是b▮索引5对应值是c▮索引6对应值是1▮索引7对应值是2▮索引8对应值是3
▮键name对应值zs▮键age对应值18▮键web对应值www.baidu.com
-->

<div id="app">
<span v-for="(value,index) in name">▮索引{{index}}对应值是{{value}}</span>
<hr>
<span v-for="(value,index) in '字符串abc123'">▮索引{{index}}对应值是{{value}}</span>
<hr>
<span v-for="(value,key) in obj">▮键{{key}}对应值{{value}}</span>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: ["张三", "李四", "王五", "麻子"],
obj: {
name: "zs",
age: 18,
web: "www.baidu.com"
}
}
});
</script>

v-for注意点-数据混乱:

  • v-for为了提升性能,在更新已渲过的元素列表时,会采用“就地复用”策略。也正是因为这个策略,在某些时刻会导致我们的数据混乱。

Snipaste_2022-12-05_10-59-38

  • 为了解决这个问题,我们可以在渲染列表的时候给每一个元素加上一个独一无二的key。v-for在更新已渲染过的元素列表时,会先判断key是否相同,如果相同则复用,不同则重新创建
    • key属性注意点:不能用索引index作为key,因为当列表的内容新增或者删除时index都会发生变化。这就导致了不能很好地复用没有发生改变的元素,大大降低了渲染效率。

Snipaste_2022-12-05_11-00-09

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
<div id="app">
<form>
<input type="text" v-model="name">
<input type="submit" value="添加" @click.prevent="add">
</form>
<ul>
<li v-for="(person,index) in persons" :key="person.id">
<input type="checkbox">
<span>{{index}} --- {{person.name}}</span>
</li>
</ul>
</div>
<script>
let vue = new Vue({
el: '#app',
data: {
persons: [
{ name: "zs", id: 1 },
{ name: "ls", id: 2 },
{ name: "ww", id: 3 }
],
name: ""
},
methods: {
add() {
let lastPerson = this.persons[this.persons.length - 1];
let newPerson = { name: this.name, id: lastPerson.id + 1 };
// this.persons.push(newPerson);
this.persons.unshift(newPerson);
this.name = "";
}
},
computed: {}
});
</script>

v-bind

在企业开发中想要给“元素”绑定数据,可以使用{{}}v-textv-html。但是如果想给“元素的属性”绑定数据,就必须使用v-bind。v-bind 的作用就是专门用于给“元素的属性”绑定数据的

对于大部分的属性而言,我们只需要直接赋值就可以,例如: :value="name"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div id="app">
<input type="text" v-bind:value="name">
<!-- v-bind可以省略 -->
<input type="text" :value="name">
<input type="text" :value="age">
<!-- 绑定的数据只要是合法的表达式也都可以 -->
<input type="text" :value="age+1">
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "zs",
age: 18
}
});
</script>

绑定类名 class

格式: :class="['类名',...]"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<style>
.size {font-size: 48px;}
.color {color: red;}
.bg {background: blue;}
</style>
<div id="app">
<!-- 不能直接复制,默认情况下会去Model中查找数据,但是Model中没有对应的类名,所以无效 -->
<p :class="size">我是段落</p>
<!-- 正确 -->
<p :class=['size','color','bg']>我是段落</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
flag:true
}
});
</script>

绑定样式 style

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div id="app">
<!-- 样式值要用引号''括起来 -->
<p :style="{color:'red'}">段落P</p>
<!-- 样式名称带 - ,也必须要用引号''括起来 -->
<p :style="{'font-size':'100px'}">段落P</p>

<p :style="obj1">段落P</p>
<p :style="[obj1,obj2]">段落P</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
obj1: {
"font-size": "50px",
"color": "red"
},
obj2: {
"background": "blue"
}
}
});
</script>

v-on

绑定监听事件

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
<div id="app">
<!-- 不传递参数可以不写() -->
<button v-on:click="fn1">我是按钮</button>
<button @click.once="fn1">我是fn1</button>]
<!-- 传递参数 -->
<button @click="fn2('zs',18)">我是fn2</button>
<!-- 传递原生的事件对象 -->
<button @click="fn3($event)">我是fn3</button>
<!-- 在绑定的函数中用到data中的数据必须加上this -->
<button @click="fn4">我是fn4</button>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "zs"
},
//专门用于存储监听事件回调函数
methods: {
fn1() {
alert("fn1被点击了")
},
fn2(name, age) {
console.log(name, age);
},
fn3(e) {
console.log(e);
},
fn4() {
console.log(this.name);
}
}
});
</script>

事件修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<button @click.once="fn1">我是fn1</button>

.once ——最多触发一次处理函数。
.prevent ——调用 event.preventDefault();默认事件不会执行,比如a链接跳转
.stop ——调用 event.stopPropagation();阻止事件冒泡
.self ——只有事件从元素本身发出才触发处理函数。
.capture ——在捕获模式添加事件监听器;事件冒泡变成事件捕获
.passive ——通过 { passive: true } 附加一个 DOM 事件。

###鼠标按键修饰符
.left ——只在鼠标左键事件触发处理函数。
.right ——只在鼠标右键事件触发处理函数。
.middle ——只在鼠标中键事件触发处理函数。
.{keyAlias} ——只在某些按键下触发处理函数。

按键修饰符:监听特定按键触发的事件

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
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right

<div id="app">
<!--系统内置的直接使用-->
<input type="text" @keyup.enter="fn1">
<!--使用自定义-->
<input type="text" @keyup.f2="fn1">
</div>

<script>
//自定义全局修饰符名称
//Vue.config.修饰符名称 = keycode(键码值)
Vue.config.keyCodes.f2 = 113
const vue = new Vue({
el: '#app',
methods: {
fn1() {
alert("按键事件触发了")
}
}
});
</script>

v-model

数据双向传递,只有 <input> <textarea> <select> 元素上可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- input输入李四,下面的P标签也变成李四 -->
<div id="app">
<input type="text" v-model="name">
<p>我的姓名是{{name}}</p>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
name: "zs"
}
});
</script>

v-once

vue默认数据绑定:当数据发生变化,界面也会随着变化。v-once可以控制该元素和组件只渲染一次。随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过。这可以用于优化更新性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 单个元素 -->
<span v-once>{{msg}}</span>

<!-- 带有子元素的元素 -->
<div v-once>
<h1>comment</h1>
<p>{{msg}}</p>
</div>

<!-- 组件 -->
<MyComponent v-once :comment="msg" />

<!-- v-for 指令 -->
<ul>
<li v-for="i in list" v-once>{{i}}</li>
</ul>

v-cloak

1
2
3
4
5
6
7
8
9
10
<p>{{msg}}</p>
<!-- vue先加载未绑定数据的界面,即显示{{msg}} -->
<!-- 数据绑定后,替换并显示msg内容 -->

<!-- v-cloak 配合css中的display:none,可以先不显示,等数据绑定替换后,再显示最终绑定数据的代码 -->
<style>
[v-cloak] {display: none;}
</style>

<p v-cloak>{{msg}}</p>

自定义指令

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
<div id="app">
<p v-color="'red'">自定义指令</p>
</div>

<script>
//自定义全局指令
Vue.directive("color", {
//这里的el就是被绑定指令的那个元素
bind: function (el, binding) {
el.style.color = binding.value;
}
})
const vue = new Vue({
el: '#app',
data: {},
methods: {},
//专门用于定义局部指令的(只有#app能用)
directives: {
"color": {
//这里的el就是被绑定指令的那个元素
bind: function (el, binding) {
el.style.color = binding.value;
}
}
}
});
</script>

<!--
el:指令绑定到的元素。这可以用于直接操作 DOM。
binding:一个对象,包含以下属性。
value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2。
oldValue:之前的值,仅在 beforeUpdate 和 updated 中可用。无论值是否更改,它都可用。
arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。
modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }。
instance:使用该指令的组件实例。
dir:指令的定义对象。
-->

过度动画

使用默认类名CSS

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
77
78
79
80
81
82
<style>
.box {
width: 100px;
height: 100px;
background-color: bisque;
}

/* 默认 */
.v-enter {
opacity: 0;
}
.v-enter-to {
opacity: 1;
}
.v-enter-active {
transition: all 3s;
}
.v-leave {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
.v-leave-active {
transition: all 2s;
}

/* 绑定name="one" */
.one-enter {
opacity: 0;
}
.one-enter-to {
opacity: 1;
}
.one-enter-active {
transition: all 10s;
}
.one-leave {
opacity: 1;
}
.one-leave-to {
opacity: 0;
}
.one-leave-active {
transition: all 10s;
}
</style>

<div id="app">
<button @click="toggle">我是按钮</button>

<!-- 一个transition组件只能跟一个元素 -->
<transition>
<div class="box" v-show="isShow"></div>
</transition>
<!-- appear可以一进入就显示过度动画 -->
<transition appear>
<div class="box" v-show="isShow"></div>
</transition>

<!-- 使用name绑定不同的动画 -->
<transition appear name="one">
<div class="box" v-show="isShow"></div>
</transition>
<transition appear name="one">
<div class="box" v-show="isShow"></div>
</transition>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
isShow: false
},
methods: {
toggle() {
this.isShow = !this.isShow;
}
}
});
</script>

JavaScript 钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"

v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
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
<div id="app">
<button @click="toggle">我是按钮</button>
<!-- 注意点:通过JS钩子函数来实现过渡动画
但是vue还是会去查找类名,所以为了不让vue去查找类名
可以给transition添加 v-bind:css="false" -->
<transition
v-bind:css="false"
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
>
<div class="box" v-show="isShow"></div>
</transition>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
isShow: false
},
methods: {
toggle() {
this.isShow = !this.isShow;
},
//el就是当前需要执行动画的元素
beforeEnter(el) {
el.style.opacity = "0";
},
enter(el, done) {
el.offsetWidth;
// 注意点:通过js钩子函数来实现过渡动画
// 那么必须在动画执行过程中的回调函数中写上
// el.offsetWidth/el.offsetHeight
el.style.transition = "all 3s";
//动画执行完毕之后一定要调用done回调函数
//否则后续的afterEnter钩子函数不会被执行
done();
},
afterEnter(el) {
el.style.opacity = "1";
}
}
});
</script>

Animate.css

自定义过度类名

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
enter-class
enter-active-class
enter-to-class

leave-class
leave-active-class
leave-to-class

<!------------------------------->

<style>
.box {
width: 100px;
height: 100px;
background-color: bisque;
}

.a {opacity: 0;}
.c {opacity: 1;}
.b {transition: all 3s;}
</style>

<div id="app">
<button @click="toggle">我是按钮</button>
<transition enter-class="a" enter-active-class="b" enter-to-class="c">
<div class="box" v-show="isShow"></div>
</transition>
</div>

结合Animate.css

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
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" rel="stylesheet" type="text/css">

<div id="app">
<button @click="toggle">我是按钮</button>
<!--
使用bounce动画
https://animate.style/
-->
<transition enter-active-class="animated bounce">
<div class="box" v-show="isShow"></div>
</transition>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
isShow: false
},
methods: {
toggle() {
this.isShow = !this.isShow;
}
}
});
</script>

列表动画

transition只能添加一个元素,如果有多个元素,可以使用 transition-group

  • 子元素必须要有唯一的key属性,且key不能为索引值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<button @click="toggle">我是按钮</button>
<!-- transition-group默认会创建一个span包裹,通过tag可以创建指定标签 -->
<transition-group enter-active-class="animated bounce" tag="div">
<div class="box animated bounce" v-show="isShow" :key="1"></div>
<div class="box animated bounce" v-show="isShow" :key="2"></div>
<div class="box animated bounce" v-show="isShow" :key="3"></div>
</transition-group>
</div>

<script>
const vue = new Vue({
el: '#app',
data: {
isShow: false
},
methods: {
toggle() {
this.isShow = !this.isShow;
}
}
});
</script>

组件

组件命名注意点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1.注册组件的时候使用了驼峰命名,那么在使用组件的时候需要转换成短横线分割命名
<!--使用组件-->
<use-com></use-com>
<!--注册组件-->
Vue.components("useCom",....)

2.传递数据的时候如果想使用驼峰命名,那么就必须写短横线分割命名
<!--传递时-->
<son :father-name="name"></son>
<!--接收时-->
props: ["fatherName"]

3.传递方法的时候不能使用驼峰命名,只能使用短横线分割命名
<!--传递时-->
<son @father-say="say"></son>
<!--子组件-->
this.$emit("father-say");

全局组件

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
<div id="app">
<!-- 3.使用注册好的组件 -->
<news></news>
</div>

<script>
//1.创建组件构造器
var Profile = Vue.extend({
// 注意点:在创建组件指定组件的模板的时候,模板只能有一个根元素
template: `
<div>
<img src = "abc.jpg"/>
<p>我是描述信息</p>
</div>
`
});
// 2.注册已经创建好的组件 Vue.component( id, [definition] )
Vue.component("news", Profile);

const vue = new Vue({
el: '#app',
data: {
name: "zs"
}
});
</script>

<!-- 字符串模板没有提示,不利于编写代码 使用<template>标签 -->

<div id="app">
<!-- 3.使用注册好的组件 -->
<news></news>
</div>

<template id="info">
<div>
<img src="abc.jpg" />
<p>我是描述信息</p>
</div>
</template>

<script>
//1.创建组件构造器
var Profile = Vue.extend({
// 注意点:在创建组件指定组件的模板的时候,模板只能有一个根元素
template: "#info"
});
// 2.注册已经创建好的组件 Vue.component( 指定注册组件的名称, [传入已经注册好的组件构造器] )
Vue.component("news", Profile);

const vue = new Vue({
el: '#app',
data: {
name: "zs"
}
});
</script>

简写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<news></news>
</div>

<template id="info">
<div>
<img src="abc.jpg" />
<p>我是描述信息</p>
</div>
</template>

<script>
Vue.component("news", {
template: "#info"
});

const vue = new Vue({
el: '#app',
data: {
name: "zs"
}
});
</script>

局部组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app">
<abc></abc>
</div>

<template id="info">
<div>
<img src="abc.jpg" />
<p>我是描述信息</p>
</div>
</template>

<script>
const vue = new Vue({
el: '#app',
data: {},
//专门用于定义局部组件的,只能在当前id为#app那个元素中使用
components: {
"abc": {
template: "#info"
}
}
});
</script>

组件中的data和methods

vue实例控制的区域相当于一个大的组件,在大的组件中我们可以使用data和methods。

而我们自定义的组件也是一个组件,所以在自定义的组件中也能使用data和methods

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
<div id="app">
<abc></abc>
</div>

<template id="info">
<div>
<button @click="appFn">我是按钮</button>
<p>{{msg1}}</p>
<img src="abc.jpg" />
</div>
</template>

<script>
Vue.component("abc", {
template: "#info",
methods: {
appFn() {
alert("被点击了");
}
},
data: function () {
return {
msg1: "自定义组件中使用data必须赋值一个函数,然后通过函数的返回值来定义",
msg2: "-------------------------------",
msg3: "组件中的data如果不是通过函数返回的,那么多个组件就会共用一份数据,就会导致数据混乱。",
msg4:"通过函数返回,那么每创建一个新的组件,都会调用一次这个方法",
msg5:"将这个方法返回的数据和当前创建的组件绑定在一起,这样就有效的避免了数据混乱"
}
}
});
const vue = new Vue({
el: '#app',
data: {},
});
</script>

组件切换

动态组件是专门用来切换组件的,也就是你让我显示谁,我就显示谁。

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
<component v-bind:is="需要显示组件的名称"></component>

<!------------------------------>

<div id="app">
<component v-bind:is="'abc'"></component>

<button @click="qiehuan">切换页面</button>
<!-- <keep-alive>可以保存上一次的状态 -->
<keep-alive>
<component v-bind:is="name"></component>
</keep-alive>

</div>

<template id="home">
<div>
<h1>我是首页</h1>
<input type="checkbox">
</div>
</template>

<template id="post">
<div>
<h1>我是文章页</h1>
</div>
</template>

<script>
Vue.component("abc", {
template: "#home"
});
Vue.component("xyz", {
template: "#post"
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
},
methods: {
qiehuan() {
this.name = this.name === "abc" ? "xyz" : "abc"
}
}
});
</script>

组件动画

给组件添加动画和过去给元素添加动画一样。如果是单个组件组件就使用,多个组件就使用

  • 默认情况下进入动画和离开动画是同时执行的,如果想一个做完之后再做另一个,需要指定动画模式
    • in-out:新元素先进行过渡,完成之后当前元素过渡离开。
    • out-in:当前元素先进行过渡,完成之后新元素过渡进入。
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
<div id="app">
<button @click="qiehuan">切换页面</button>
<!-- mode="out-in" 指定过渡模式,先离开再进入 -->
<transition mode="out-in">
<component v-bind:is="name"></component>
</transition>
</div>

<template id="home">
<div>
<h1>我是首页</h1>
<input type="checkbox">
</div>
</template>

<template id="post">
<div>
<h1>我是文章页</h1>
</div>
</template>

<script>
Vue.component("abc", {
template: "#home"
});
Vue.component("xyz", {
template: "#post"
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
},
methods: {
qiehuan() {
this.name = this.name === "abc" ? "xyz" : "abc"
}
}
});
</script>


<style>
.v-enter {
opacity: 0;
}
.v-enter-to {
opacity: 1;
}
.v-enter-active {
transition: all 3s;
}
.v-leave {
opacity: 1;
}
.v-leave-to {
opacity: 0;
}
.v-leave-active {
transition: all 2s;
}
</style>

父子组件

在一个组件中又定义了其它组件,就是父子组件。

  • 局部组件就是最简单的父子组件,因为Vue实例可以看做是一个大组件,在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
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
77
78
79
80
81
82
83
84
85
86
<!--在全局组件中-------------------------------------------->
<div id="app">
<father>
<!-- 在这里使用son组件不起作用-->
<son></son>
</father>
</div>

<template id="father">
<div>
<h1>我是father组件</h1>
<!-- 在这里使用才行-->
<son></son>
</div>
</template>

<template id="son">
<div>
<h2>我是son组件</h2>
</div>
</template>

<script>
Vue.component("father", {
template: "#father",
// conponents定义的组件,只能在定义它的那个组件中使用
components: {
"son": {
template: "#son"
}
}
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>


<!--在局部组件中-------------------------------------------->
<div id="app">
<father>
<!-- 在这里使用son组件不起作用-->
<son></son>
</father>
</div>

<template id="father">
<div>
<h1>我是father组件</h1>
<!-- 在这里使用才行-->
<son></son>
</div>
</template>

<template id="son">
<div>
<h2>我是son组件</h2>
</div>
</template>

<script>
//爷爷组件
const vue = new Vue({
el: '#app',
data: {
name: "abc"
},
components: {
//父组件
"father": {
template: "#father",
// conponents定义的组件,只能在定义它的那个组件中使用
//孙子组件
components: {
"son": {
template: "#son"
}
}
}
}
});
</script>

父子组件-传递数据

Vue中子组件是不能访问父组件数据的。子组件想要访问父组件的数据,必须通过父组件传递

  • 先在父组件中通过 v-bind 传递数据: v-bind:自定义接收名称 = “要传递数据”
  • 然后在组件中中通过 props 接收数据: props:[“自定义接收名称”]
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
<div id="app">
<father></father>
</div>

<template id="father">
<div>
<!-- 父组件传递数据 可省略v-bind,简写-->
<son v-bind:fathername="name" v-bind:fathersite="site"></son>
<son :fathername="name" :fathersite="site"></son>
</div>
</template>

<template id="son">
<div>
<!-- 子组件使用数据 -->
<p>{{fathername}}</p>
<p>{{fathersite}}</p>
</div>
</template>

<script>
//父组件
Vue.component("father", {
template: "#father",
data: function () {
return {
name: "百度",
site: "www.baidu.com"
}
},
// 子组件
components: {
"son": {
template: "#son",
//子组件接收数据
props: ["fathername", "fathersite"]
}
}
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

父子组件-传递方法

和传递数据不同,传递方法子组件不需要接收。只需要在子组件中自定义一个方法,通过 this.$emit(‘自定义接收名称’) 的方法来触发父组件传递过来的方法。

  • 在父组件中通过 v-on 传递方法: v-on:自定义接收名称 = "要传递方法"

  • 在子组件中通过 this.$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
<div id="app">
<father></father>
</div>

<template id="father">
<div>
<button @click="say">我是父组件</button>
<!-- 父组件传递方法 -->
<son v-on:fathersay="say"></son>
<!-- v-on 简写 @ -->
<son @fathersay="say"></son>
</div>
</template>

<template id="son">
<div>
<!-- 子组件直接使用自己定义的方法 -->
<button @click="sonsay">我是子组件</button>
</div>
</template>

<script>
//父组件
Vue.component("father", {
template: "#father",
methods: {
say() {
alert("hello");
}
},
// 子组件
components: {
"son": {
template: "#son",
//子组件自定义方法-触发父组件传递过来的方法
methods: {
sonsay() {
this.$emit("fathersay");
}
}
}
}
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

子组件传递数据给父组件

既然父组件的方法可以传递给子组件,子组件可以调用父组件中的方法;那么我们就可以在调用方法的时候给方法传递参数,传递的参数就是我们需要传递的数据。

多级传递

在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
<div id="app">
<father>
<!-- 默认father组件中动态的增加内容是不会显示的 -->
<div>我是新追加的内容</div>
<p>这里写多少就会显示多少,即这里N个内容对应下面一个slot</p>
</father>
</div>

<template id="father">
<div>
<div>我是头部</div>
<!--
需要组件插入 <slot> 标签
这个标签就是一个坑
可以设置默认内容,上面没有动态增加内容不会被替换
上面增加动态内容就会替换掉默认内容
-->
<slot>我是匿名插槽默认内容</slot>
<div>我是底部</div>
</div>
</template>

<script>
//父组件
Vue.component("father", {
template: "#father",
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

具名插槽

多级组件中都可以使用

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
<div id="app">
<father>
<!--这里通过slot属性告诉vue,当前的内容是要填充到哪一个插槽中 -->
<div slot="1">百度</div>
<p slot="abc">www.baidu.com</p>
<p slot="abc">www.baidu.com</p>
<p slot="abc">www.taobao.com</p>
</father>
</div>

<template id="father">
<div>
<div>头部</div>
<!-- 给slot增加name,就是具名插槽-->
<slot name="1">我是具名插槽1 默认内容</slot>
<div>内容</div>
<slot name="abc">我是具名插槽abc 默认内容</slot>
<div>底部</div>
</div>
</template>

<script>
//父组件
Vue.component("father", {
template: "#father",
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

v-slot 指令

Vue 2.6.0版本之后,新增了 v-slot 指令,用于替换具名插槽中的 slot 属性

  • 必须写在template标签中
  • v-slot:1 可以简写成 #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
<div id="app">
<father>
<!--v-slot 必须写在 template 标签中 -->
<template v-slot:1>
<div>百度</div>
<div>淘宝</div>
</template>
<!-- v-slot 可以简写成 # -->
<template #abc>
<p>www.baidu.com</p>
<p>www.taobao.com</p>
</template>

</father>
</div>

<template id="father">
<div>
<div>头部</div>
<!-- 给slot增加name,就是具名插槽-->
<slot name="1">我是具名插槽1 默认内容</slot>
<div>内容</div>
<slot name="abc">我是具名插槽abc 默认内容</slot>
<div>底部</div>
</div>
</template>

<script>
//父组件
Vue.component("father", {
template: "#father",
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

作用域插槽

作用域插槽就是带数据的插槽,就是让父组件在填充子组件插槽内容时也能使用子组件的数据。

应用场景:子组件提供数据,父组件决定如何渲染

  • 在子组件slot中通过 v-bind:数据名称="数据名称" 方式暴露数据
  • 在父组件中通过 <template slot-scope="作用域名称"> 接收数据
    • 2.6.0版本之后,v-slot 指令替换掉了 slot-scope
  • 在父组件的 <template></template> 中通过 作用域名称.数据名称 方式使用数据
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
<div id="app">
<father>
</father>
</div>

<template id="father">
<div>
<son>
<!-- 接收子组件插槽暴露的数据 -->
<!-- <template slot-scope="abc"> -->
<!-- <template v-slot:default="abc"> -->
<template #default="abc">
<!-- 使用子组件数据 -->
<div>我是填充内容 {{abc.sonname}}</div>
<li v-for="(xyz,index) in abc.sonname">{{xyz}}</li>
</template>
</son>
</div>
</template>

<template id="son">
<div>
<p>我是头部{{name}}</p>
<!-- v-bind:sonname="name" 暴露子组件数据name -->
<slot v-bind:sonname="name">我是插槽默认内容{{name}}</slot>
<p>我是底部</p>
</div>
</template>

<script>
//父组件
Vue.component("father", {
template: "#father",
components: {
//子组件
"son": {
template: "#son",
data: function () {
return {
name: ["666", "777", 888, 999]
}
}
}
}
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

image-20221211200501886

vuex

vuex是vue配套的公共数据管理工具,我们可以将共享的数据保存到vuex中,方便整个程序中的任何组件都可以获取和修改vuex中保存的公共数据。

state属性

用于保存共享数据

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
<!--vuex3.0版本使用方式------------------------------------------------------->

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 😍0.导入vuex之前必须先导入vue -->
<!-- 😍1.导入vuex -->
<script src="https://unpkg.com/vuex@3"></script>

<div id="app">
<grandfather>
</grandfather>
</div>

<template id="grandfather">
<div>
<!-- 😍4.使用共享数据 -->
<p>{{this.$store.state.name}}</p>
<p></p>
<father></father>
</div>

</template>

<template id="father">
<div>
<p>{{this.$store.state.site}}</p>
<son></son>
</div>
</template>

<template id="son">
<div>
<p>{{this.$store.state.name}}的网址是{{this.$store.state.site}}</p>
</div>
</template>

<script>
// 😍2.创建vuex对象
const store = new Vuex.Store({
// 这里的state,就相当于组件中的data,就是专门用于保存共享数据的
state: {
name: "百度",
site: "www.baidu.com"
},
});
//爷爷组件
Vue.component("grandfather", {
template: "#grandfather",
// 😍3.在祖先组件中添加stored的key保存Vuex对象
// 祖先组件中保存了vuex对象,祖先组件和所有的后代组件都可以使用vuex中保存的共享数据
store: store,
// 爸爸组件
components: {
"father": {
template: "#father",
// 儿子组件
components: {
"son": {
template: "#son",
}
}
}
}
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

mutations属性

用于保存修改共享数据的方法

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3"></script>

<div id="app">
<grandfather>
</grandfather>
</div>

<template id="grandfather">
<div>
<button @click="add">增加</button>
<button @click="sub">减少</button>
<input type="text" :value="this.$store.state.age">
<father></father>
</div>

</template>

<template id="father">
<div>
<button @click="add">增加</button>
<button @click="sub">减少</button>
<input type="text" :value="this.$store.state.age">
</div>
</template>

<script>
const store = new Vuex.Store({
// 用于保存共享数据
state: {
name: "百度",
age: 18,
},
// 用于保存修改共享数据的方法
mutations: {
// 执行mutations中定义的方法的时候,系统会自动给这些方法传递一个state参数
// state中就保存了共享的数据
mAdd(state) {
state.age = state.age + 1;
},
mSub(state) {
state.age = state.age - 1;
}
}
});
//爷爷组件
Vue.component("grandfather", {
template: "#grandfather",
store: store,
methods: {
add() {
// 调用mutations中的方法
this.$store.commit("mAdd");
},
sub() {
this.$store.commit("mSub");
}
},
// 爸爸组件
components: {
"father": {
template: "#father",
methods: {
add() {
this.$store.commit("mAdd");
},
sub() {
this.$store.commit("mSub");
}
},
}
}
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

getters属性

用于保存计算属性。和Vue实例中的 computed 效果一样。以后想要把数据缓存起来:

  • 组件中就用computed
  • Vuex中就用getters
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
<div id="app">
<father>
</father>
</div>

<template id="father">
<div>
<!-- 控制台只输出一次 被执行了 -->
<p>{{this.$store.getters.pinjie}}</p>
<p>{{this.$store.getters.pinjie}}</p>
<p>{{this.$store.getters.pinjie}}</p>
</div>
</template>

<script>
const store = new Vuex.Store({
// 用于保存共享数据
state: {
name: "百度",
},
// 用于保存修改共享数据的方法
mutations: {},
// 用于保存计算属性
getters: {
pinjie(state) {
console.log("getters方法被执行了");
return state.name + "www.baidu.com";
}
}
});

Vue.component("father", {
template: "#father",
store: store,
});

const vue = new Vue({
el: '#app',
data: {
name: "abc"
}
});
</script>

Vue Router

Vue Router和 v-if/v-show 一样,是用来切换组件显示的。

  • v-if/v-show 是标记来切换(true/false)
  • Vue Router用哈希来切换(#/xxx)
  • 比 v-if/v-show 强大的是,Vue Router不仅能够切换组件的显示,还能够在切换的时候传递参数
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
<!--Vue Rouer 3.x 版本-------------------------------------------------------->

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<!-- 0.必须先导入Vue之后再导入Vue Router -->
<!-- 0.导入Vue router -->
<script src="https://unpkg.com/vue-router@3"></script>

<div id="app">
<a href="#/one">切换到第1个页面</a>
<!--不推荐写成a标签,更推荐router-link标签-->
<!-- router-link默认渲染成a链接,tag可以指定渲染成xx标签 -->
<router-link to="/one" tag="button">切换到第1个页面</router-link>
<router-link to="/two" tag="div">切换到第2个页面</router-link>
<!-- 路由出口 路由匹配到的组件将渲染在这里-->
<router-view></router-view>
</div>

<template id="one">
<div>
<h1>我是第一个页面</h1>
</div>
</template>

<template id="two">
<div>
<h1>我是第二个页面</h1>
</div>
</template>

<script>
//😍1.定义组件
const one = {
template: "#one"
};
const two = {
template: "#two"
}
// 😍2.定义切换的规则(定义路由规则)
const routes = [
// 把根目录重定向到某个页面
{ path: "/", redirect: '/one' },
// 数组中的每一个对象就是一条规则
{ path: "/one", component: one },
{ path: "/two", component: two },
];
// 😍3.根据自定义的切换规则创建路由对象
const router = new VueRouter({
routes: routes,
// 给被激活的路由对象重新添加一个类名
// 之后可以给这个类名添加css,达到突出显示被激活路由的目的
linkActiveClass: "zdy-active",
});

const vue = new Vue({
el: '#app',
// 😍4.将创建好的路由对象绑定到Vue实例上
router: router,
//注册一个局部组件(也可以注册全局组件)
components: {
one: one,
two: two
}
});
</script>

传递参数

只要将Vue Router挂载到了Vue实例对象上,我们就可以通过vue.$route拿到路由对象;只要能拿到路由对象,就可以通过路由对象传递参数

  • 方式1:通过URL参数(?key=value&key=value) 传递,通过 this.$route.query 获取
  • 方式2:通过占位符传递(路由规则中 /:key/:key ,路径中 /value/value) ,通过 this.$route.params 获取
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
<div id="app">
<!-- 1️⃣方法1 -->
<router-link to="/one?name=百度&site=www.baidu.com" tag="button">切换到第1个页面</router-link>
<!-- 2️⃣方法2 -->
<router-link to="/two/淘宝/www.taobao.com" tag="button">切换到第2个页面</router-link>
<router-view></router-view>
</div>

<template id="one">
<div>
<h1>我是第一个页面</h1>
</div>
</template>

<template id="two">
<div>
<h1>我是第二个页面</h1>
</div>
</template>

<script>
const one = {
template: "#one",
// 方法1获取
created: function () {
console.log(this.$route);
console.log(this.$route.query.name);
console.log(this.$route.query.site);
}
};
const two = {
template: "#two",
// 方法2获取
created: function () {
console.log(this.$route);
console.log(this.$route.params.name);
console.log(this.$route.params.site);
}
}
const routes = [
{ path: "/", redirect: '/one' },
{ path: "/one", component: one },
// 1️⃣方法2
{ path: "/two/:name/:site", component: two },
];
const router = new VueRouter({
routes: routes,
});

const vue = new Vue({
el: '#app',
router: router,
components: {
one: one,
two: two
}
});
</script>

image-20221212232553323

嵌套路由

嵌套路由也称之为子路由,就是在被切换的组件中又切换其它子组件

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
77
78
79
80
81
82
83
84
<div id="app">
<router-link to="/one" tag="button">切换到第1个页面</router-link>
<router-link to="/two" tag="button">切换到第2个页面</router-link>
<router-view></router-view>
</div>

<template id="one">
<div>
<h1>我是第一个页面</h1>
<router-link to="/one/onechildren1" tag="button">切换到第一个子界面</router-link>
<router-link to="/one/onechildren2" tag="button">切换到第二个子界面</router-link>
<router-view></router-view>
</div>
</template>

<template id="onechildren1">
<div>
<h1>我是第一个页面的子页面1</h1>
</div>
</template>

<template id="onechildren2">
<div>
<h1>我是第一个页面的子页面2</h1>
</div>
</template>

<template id="two">
<div>
<h1>我是第二个页面</h1>
</div>
</template>

<script>
//同样也是先定义组件
const onechildren1 = {
template: "#onechildren1",
};
const onechildren2 = {
template: "#onechildren2",
};
const one = {
template: "#one",
//将组件添加到这个父组件中,成为子组件
components: {
onechildren1: onechildren1,
onechildren2: onechildren2
}
};
const two = {
template: "#two",
}
const routes = [
{
path: "/one",
component: one,
//通过children配置子路由
children: [
{
// 子路由不用写一级路由的地址,也不用写/
path: "onechildren1",
component: onechildren1
},
{
path: "onechildren2",
component: onechildren2
}
]
},
{ path: "/two", component: two },
];
const router = new VueRouter({
routes: routes,
});

const vue = new Vue({
el: '#app',
router: router,
components: {
one: one,
two: two
}
});
</script>

命名视图

命名视图和前面讲的插槽很像,都是让不同的出口显示不同的内容。命名视图就是当路由地址被匹配的时候指定多个出口,并且每个出口中显示的内容不同。

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
<div id="app">
<!-- 通过name属性 指定路由出口显示不同的内容 -->
<router-view name="name1"></router-view>
<router-view name="name2"></router-view>
</div>

<template id="one">
<div>
<h1>我是第一个页面</h1>
</div>
</template>

<template id="two">
<div>
<h1>我是第二个页面</h1>
</div>
</template>

<script>
const one = {
template: "#one",
};
const two = {
template: "#two",
}
const routes = [
{
path: "/",
components: {
// key: value
name1: one,
name2: two
}
},
];
const router = new VueRouter({
routes: routes,
});

const vue = new Vue({
el: '#app',
router: router,
//同时显示多个组件,给它传一个对象, key:value
components: {
one: one,
two: two
}
});
</script>

监听路由变化

Vue实例中,有一个watch属性,专门用于监听数据变化的,只要数据发生了变化,就会自动调用数据的回调方法

Watch属性还可以监听路由地址的变化;在企业开发中我们可以通过watch来判断当前界面是从哪个界面跳转过来的

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
<div id="app">
<!-- 监听数据变化 -->
<input type="text" v-model="num1">+
<input type="text" v-model="num2">=
<input type="text" disabled v-model="res">
<!-- 监听路由变化 -->
<router-link to="/one" tag="button">切换到第1个页面</router-link>
<router-link to="/two" tag="button">切换到第2个页面</router-link>
<router-view></router-view>
</div>

<template id="one">
<div>
<h1>我是第一个页面</h1>
</div>
</template>

<template id="two">
<div>
<h1>我是第二个页面</h1>
</div>
</template>

<script>
const one = {
template: "#one",
};
const two = {
template: "#two",
}
const routes = [
{ path: "/one", component: one },
{ path: "/two", component: two },
];
const router = new VueRouter({
routes: routes,
});

const vue = new Vue({
el: '#app',
router: router,
data: {
num1: 0,
num2: 0,
res: 0
},
watch: {
// 监听数据的变化 xx数据变化就会调用这个函数
num1: function () {
this.res = parseInt(this.num1) + parseInt(this.num2);
},
num2: function () {
this.res = parseInt(this.num1) + parseInt(this.num2);
},

// 监听路由变化 xx路由地址变化就会调用这个函数
// 调用这个函数,默认会传递两个参数,第一个参数代表改变后的数据,第二个参数代表改变前的数据
"$route.path": function (newValue, oldValue) {
console.log(newValue, oldValue);
}
},
components: {
one: one,
two: two
}
});
//创建的路由对象挂在到vue实例上,就可以通过 vue.$route 拿到挂在的路由对象
console.log(vue.$route);
</script>

ref操作DOM

不推荐原生的语法操作。推荐

  • 在元素或者组件中,添加 ref=xxx 属性
  • 在使用的地方通过 this.$refs.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
33
34
35
36
37
38
39
40
41
42
43
44
<div id="app">
<button @click="myFn">按钮</button>
<!-- ref添加给元素 -->
<input v-model="num" ref="myInput">
<!-- ref添加给自定义的组件 -->
<one ref="myOne"></one>
</div>

<template id="one">
<div>
<h1>我是组件</h1>
</div>
</template>

<script>
Vue.component("one", {
template: "#one",
data: function () {
return {
msg: "百度",
}
}
});

const vue = new Vue({
el: '#app',
data: {
num: 0
},
methods: {
myFn() {
// 获取到所有有ref属性的元素或者组件
console.log(this.$refs);
// 拿到的是元素
console.log(this.$refs.myInput); //<input ...>
console.log(this.$refs.myInput.value); //0
// 拿到的是自定义的组件
// 可以获取组件中的data和methods
console.log(this.$refs.myOne);
console.log(this.$refs.myOne.msg); //百度
}
}
});
</script>

Vue-CLI

Vue-CLI是官方提供的脚手架工具,默认已经帮我们安装好了一套利用webpack管理vue的项目结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//全局安装Vue-cli
npm install -g @vue/cli

//检查安装成功 @vue/cli 5.0.8
vue --version

//创建项目 xxx项目名称(文件夹)
vue create xxx

//运行项目
npm run serve

//打包编译项目
npm run build

项目结构解读:

  • node_modules:存储了相关的依赖包
  • public:任何放置在public文件夹的静态资源都会被简单的复制,而不经过webpack。一般用于存储一些永远不会改变的静态资源或者webpack不支持的第三方库
  • src:代码文件夹
    • assets:存储项目中自己的一些静态文件(图片/字体等)
    • components:存储项目中的自定义组件(小组件、公共组件)
    • views:存储项目中的自定义组件(大组件、页面级组件、路由级别组件)
    • router:存储VueRouter相关文件
    • store:存储Vuex相关文件
    • App.vue:根组件
    • main.js:整个项目的入口文件
    • image-20230220140621307