19-前端核心技术-VUE基础使用

第19章-前端核心技术-VUE基础使用

学习目标

  1. 掌握VUE安装和基本使用
  2. 掌握VUE条件渲染
  3. 掌握VUE列表渲染

VUE 简介

Vue.js 是一套构建用户界面的 渐进式框架。目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。

官网地址:https://v3.cn.vuejs.org/

Vue3相比Vue2提升点

  1. 性能比Vue2.x快1.2~2倍
  • diff方法优化
    vue2中的虚拟dom是全量的对比(每个节点不论写死的还是动态的都会比较)
    vue3新增了静态标记(patchflag)与上次虚拟节点对比时,只对比带有patchflag的节点(动态数据所在的节点);可通过flag信息得知当前节点要对比的具体内容
  • 静态提升
    vue2无论元素是否参与更新,每次都会重新创建然后再渲染
    vue3对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可
  • 事件侦听器缓存
    默认情况下onClick会被视为动态绑定,所以每次都会追踪它的变化
    但是因为是同一个函数,所以不用追踪变化,直接缓存起来复用即可
  • ssr服务端渲染
    在服务器段完成渲染后再将整个html页面返回到浏览器,页面的加载时间就会加快很多
  1. 组合API(类似react hooks)
  • 将数据和业务逻辑放在setup中一起处理,而不是像vue2,将数据放在data中,业务逻辑分别放在methods,watch,computed中等
  1. 按需编译,体积比vue2.x更小
  2. 更好的Ts支持

VUE 环境搭建

下载源码

如果仅仅只是在项目或者某个文件中简单的使用vue,就可以直接在html中引入如下链接

1
2
3
<script src="https://unpkg.com/vue@next"></script>
或者
<script src="https://unpkg.com/vue@3"></script>

可以复制上面的地址到浏览器打开源代码,并复制全部源代码,保存为本地文件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
<!DOCTYPE html>
<html lang="ch">
<head>
    <meta charset="UTF-8">
    <title>VUE3</title>
    <script src="vue.global.js"></script>
</head>
<body>
<div id="app">
    Counter: {{ counter }}
</div>
</body>
<script>
    const App = {
        data() {
            return {
                counter: 0
            }
        },
        mounted() {
            setInterval(() => {
                this.counter++
            }, 1000)
        }
    }
    Vue.createApp(App).mount('#app')
</script>
</html>

VUE 语法

静态数据 data

参数类型:Object | Function,组件的定义只接受 function

当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

案例01

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>

<body>
<div id="app">来自 data 的属性值 = {{ count }}</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return { count: 4 };
},
});

const vm = app.mount("#app");

console.log(vm.$data.count); // => 4
console.log(vm.count); // => 4

// 修改 vm.count 的值也会更新 $data.count
vm.count = 5;
console.log(vm.$data.count); // => 5

// 反之亦然
vm.$data.count = 6;
console.log(vm.count); // => 6
</script>

</html>

js中的数据直接展示到页面山给,不需要再通过bom对象来获取和设置页面元素的内容,使用非常方便

当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时 data 中存在的属性才是响应式的。也就是说如果你添加一个新的属性不会起作用

动态数据 computed

参数类型:{ [key: string]: Function | { get: Function, set: Function } }

和data一样,向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性。当这些属性的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。

不同的是computed允许动态属性计算,所以一般都是用方法。

computed接受一个具有 getset 函数的对象,可以单独设置读写的方式。

1
2
3
4
5
6
7
8
9
10
const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

案例02

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>

<body>
<div id="app">
<div>从vue传过来的数据:<b>{{ count }}</b></div>
<div>库存是否足够:<b>{{ isCount }}</b></div>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return { count: 4 }
},
computed: {
isCount() {
// this 指向 vue 对象实例
return this.count > 0 ? 'Yes' : 'No'
}
},
});

const vm = app.mount("#app");
</script>

</html>

输出渲染 v-text & v-html

双大括号会将数据解释为普通文本,而非 HTML 代码。类似v-text

为了输出真正的 HTML,需要使用 v-html 指令:

案例03

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>

<body>
<div id="app">
<div>{}类似与使用v-text绑定的:<b v-text="text">{{count}}</b></div>
<div>使用v-html绑定的可以识别html标签:<p v-html="text">{{isCount}}</p>
<p>v-html 和 v-text 优先级高于 {} </p>
</div>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
count: 4,
text: '<span style="color: blue;">132456</span>'
}
},
computed: {
isCount() {
// this 指向 vue 对象实例
return this.count > 0 ? 'Yes' : 'No'
}
},
});

const vm = app.mount("#app");
</script>

</html>

绑定属性 v-bind

普通语法不能作用在 HTML 属性上,遇到这种情况应该使用 v-bind 指令:如:v-bind:id、v-bind:class、v-bind:src、v-bind:align等。

语法:v-bind:attribute

案例04

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.active {
background-color: darkcyan;
}

.error {
color: red;
}

</style>

<body>
<div id="app">
<div :class="{active: clas.isActive,'error': *****asError}">
v-bind:id="id"相当于setAttribute方法设置id属性值
</div>
<p>v-bind:id="id"可以省略为 :id</p>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
clas: {
isActive: false,
hasError: true
}
}
},
});

const vm = app.mount("#app");
</script>

</html>

绑定事件 v-on

绑定事件监听器。事件类型由参数指定。表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略。

用在普通元素上时,只能监听原生 DOM 事件。用在自定义元素组件上时,也可以监听子组件触发的自定义事件。

v-on 指令 (通常缩写为 @ 符号) 。用法为 v-on:click="methodName" 或 `@click=“methodName”

语法:v-on:event.修饰符

有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

<button @click="warn('自定义参数', $event)">Submit</button>

js部分

1
2
3
4
5
6
7
8
9
methods: {
  warn(message, event) {
    // 现在可以访问到原生事件
    if (event) {
      event.preventDefault()
    }
    alert(message)
  }
}

多事件绑定

事件处理程序中可以有多个方法,这些方法由逗号运算符分隔:

1
2
3
4
<!-- 这两个 one() 和 two() 将执行按钮点击事件 -->
<button @click="one($event), two($event)">
  Submit
</button>

js部分

1
2
3
4
5
6
7
8
9
// ...
methods: {
  one(event) {
    // 第一个事件处理器逻辑...
  },
  two(event) {
   // 第二个事件处理器逻辑...
  }
}

案例05

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.active {
background-color: darkcyan;
}

.error {
color: red;
}

</style>

<body>
<div id="app">
<button v-on:click="doClick('按钮已经被点击')">点击事件</button>
<br>
<input type="text" @keyup="doKeyup" />
<span>{{showtext}}</span><span>{{inputcount}}</span>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
showtext: '',
inputcount: 0
}
},
methods: {
//event:被触发的事件对象
doClick: function (me) {
//this:当前Vue对象
this.showtext = '字数:';
event.target.innerText = me;
},
doKeyup: function () {
this.inputcount = event.target.value.length;
}
}
});

const vm = app.mount("#app");
</script>

</html>

事件修饰符

在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。尽管我们可以在方法中轻松实现这点,但更好的方式是:方法只有纯粹的数据逻辑,而不是去处理 DOM 事件细节。

为了解决这个问题,Vue.js 为 v-on 提供了**事件修饰符**。之前提过,修饰符是由点开头的指令后缀来表示的。

  • .stop
  • .prevent
  • .capture
  • .self
  • .once
  • .passive

如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 阻止单击事件继续传播 -->
<a @click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form @submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联 -->
<a @click.stop.prevent="doThat"></a>

<!-- 只有修饰符 -->
<form @submit.prevent></form>

<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
<div @click.capture="doThis">...</div>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div @click.self="doThat">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

按键修饰符

在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 或者 @ 在监听键盘事件时添加按键修饰符:

1
2
<!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
<input @keyup.enter="submit" />

你可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符。

<input @keyup.page-down="onPageDown" />

案件 kebab-case的参考地址:https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values

Vue 为最常用的键提供了别名:

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

可以用如下修饰符来实现仅在按下相应按键时才触发鼠标或键盘事件的监听器。

  • .ctrl
  • .alt
  • .shift
  • .meta

如:

1
2
3
4
5
<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />

<!-- Ctrl + Click -->
<div @click.ctrl="doSomething">Do something</div>

.exact 修饰符

.exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。

如:

1
2
3
4
5
6
7
8
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button @click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button @click.exact="onClick">A</button>

鼠标按钮修饰符鼠标按钮修饰符

  • .left
  • .right
  • .middle

这些修饰符会限制处理函数仅响应特定的鼠标按钮

双向绑定 v-model

可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。 它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。

v-model 本质上是一个语法糖。<input v-model="test">本质如下:

1
2
<input :value="test" @input="test = $event.target.value">

其中 $event 是触发事件的元素对象,通过$event.target.value 可以获取元素的值

v-model 会忽略所有表单元素的 valuecheckedselected attribute 的初始值

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 change 事件;
  • select 字段将 value 作为 prop 并将 change 作为事件。

案例06:表单元素双向绑定

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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.active {
background-color: darkcyan;
}

.error {
color: red;
}

</style>

<body>
<div id="app">
<p>单行输入框:</p>
<input v-model="value" />
<p style="color: blue">{{ value }}</p>

<p>多行输入框:</p>
<textarea v-model="text"></textarea>
<p style="white-space: pre-line; color: blue">{{ text }}</p>

<p>复选框:</p>
<input type="checkbox" id="checkbox" v-model="checked" />
<label for="checkbox">{{ checked }}</label>

<p>单选框:</p>
<div>
<input type="radio" id="one" value="One" v-model="picked" />
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked" />
<label for="two">Two</label>
<p style="white-space: pre-line; color: blue">{{ picked }}</p>
</div>

<p>多个复选框,绑定到同一个数组:</p>
<div>
<input type="checkbox" value="Jack" v-model="checkedNames" />
<label for="jack">Jack</label>
<input type="checkbox" value="John" v-model="checkedNames" />
<label for="john">John</label>
<input type="checkbox" value="Mike" v-model="checkedNames" />
<label for="mike">Mike</label>
</div>
<p style="white-space: pre-line; color: blue">
选择的内容: {{ checkedNames }}
</p>

<p>单选下拉框框:</p>
<div id="v-model-select">
<select v-model="selected">
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p style="white-space: pre-line; color: blue">
选择的内容: {{ selected }}
</p>
</div>

<p>多选下拉框框:</p>
<div id="v-model-select">
<select v-model="selecteds" multiple>
<option disabled value="">请选择</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<p style="white-space: pre-line; color: blue">
选择的内容: {{ selecteds }}
</p>
</div>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
value: "",
text: "",
checked: true,
picked: "",
checkedNames: [],
selected: "",
selecteds: []
}
},
});

const vm = app.mount("#app");
</script>

</html>

修饰符

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组织文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

1
2
<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" />

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

<input v-model.number="age" type="number" />

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

<input v-model.trim="msg" />

条件渲染

v-if

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值的时候被渲染。

<h1 v-if="awesome">Vue is awesome!</h1>

也可以用 v-else 添加一个“else 块”:

1
2
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>

v-else-if,顾名思义,充当 v-if 的“else-if 块”,并且可以连续使用:

1
2
3
4
5
6
7
8
9
10
11
12
<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>

v-else 的用法类似,v-else-if 也必须紧跟在带 v-if 或者 v-else-if 的元素之后。

v-show

另一个用于条件性展示元素的选项是 v-show 指令。用法大致一样:

<h1 v-show="ok">Hello!</h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS property display

注意:v-show 不支持 <template> 元素,也不支持 v-else。当 v-ifv-for 一起使用时,v-if 具有比 v-for 更高的优先级。

案例07

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.active {
background-color: darkcyan;
}

.error {
color: red;
}

</style>

<body>
<div id="app">
<button @click="change()">切换</button>
<div v-if="type == '0'">
A
</div>
<div v-else-if="type == '1'">
B
</div>
<div v-else-if="type == '2'">
C
</div>
<div v-else>
Not A/B/C
</div>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
type: 0,
}
},
methods: {
//event:被触发的事件对象
change: function () {
//this:当前Vue对象
this.type = ++this.type % 4;
},
}
});

const vm = app.mount("#app");
</script>

</html>

列表渲染

v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的**别名**。

1
2
3
4
5
<ul>
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>

v-for 块中,我们可以访问所有父作用域的 property。v-for 还支持一个可选的第二个参数,即当前项的索引。

1
2
3
4
5
<ul>
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>

还可以用第三个参数作为索引:

1
2
3
<li v-for="(value, name, index) in myObject">
  {{ index }}. {{ name }}: {{ value }}
</li>

v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。

1
2
3
<div id="range" class="demo">
  <span v-for="n in 10" :key="n">{{ n }} </span>
</div>

也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

<div v-for="item of items"></div>

注意:**不**推荐在同一元素上使用 v-ifv-for

当它们处于同一节点,v-if 的优先级比 v-for 更高,这意味着 v-if 将没有权限访问 v-for 里的变量:

1
2
3
4
5
<!-- This will throw an error because property "todo" is not defined on instance. -->

<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>

可以把 v-for 移动到 <template> 标签中来修正:

1
2
3
4
5
<template v-for="todo in todos" :key="todo.name">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

案例08

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.active {
background-color: darkcyan;
}

.error {
color: red;
}

</style>

<body>
<div id="app">
<ul>
<li v-for="item in items">
{{ item }}
</li>
</ul>

<ul>
<li v-for="(item, index) in items">
{{ index }} - {{ item }}
</li>
</ul>

<ul>
<li v-for="(value, name, index) in objs">
{{ index }}. {{ name }}: {{ value }}
</li>
</ul>

<div>
<span v-for="n in 10" :key="n">{{ n }} </span>
</div>

<div v-for="item of items"></div>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
items: [1, 2, 3, 4, 5],
objs: { a: 10, b: 20, c: 30 }
}
},
methods: {
//event:被触发的事件对象
change: function () {
//this:当前Vue对象
this.type = ++this.type % 4;
},
}
});

const vm = app.mount("#app");
</script>

</html>

触发视图更新的函数

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

相比之下,也有非变更方法,例如 filter()concat()slice()。它们不会变更原始数组,而**总是返回一个新数组**。当使用非变更方法时,可以用新数组替换旧数组:

example1.items = example1.items.filter(item => item.message.match(/Foo/))

监听数据 watch

  • 类型:{ [key: string]: string | Function | Object | Array}
  • 详细:

一个对象,键是要侦听的响应式 property——包含了 data 或 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
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
const app = createApp({
  data() {
    return {
      a: 1,
      b: 2,
      c: {
        d: 4
      },
      e: 5,
      f: 6
    }
  },
  watch: {
    // 侦听顶级 property
    a(val, oldVal) {
      console.log(`new: ${val}, old: ${oldVal}`)
    },
    // 字符串方法名
    b: 'someMethod',
    // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
    c: {
      handler(val, oldVal) {
        console.log('c changed')
      },
      deep: true
    },
    // 侦听单个嵌套 property
    'c.d': function (val, oldVal) {
      // do something
    },
    // 该回调将会在侦听开始之后被立即调用
    e: {
      handler(val, oldVal) {
        console.log('e changed')
      },
      immediate: true
    },
    // 你可以传入回调数组,它们会被逐一调用
    f: [
      'handle1',
      function handle2(val, oldVal) {
        console.log('handle2 triggered')
      },
      {
        handler: function handle3(val, oldVal) {
          console.log('handle3 triggered')
        }
        /* ... */
      }
    ]
  },
  methods: {
    someMethod() {
      console.log('b changed')
    },
    handle1() {
      console.log('handle 1 triggered')
    }
  }
})

const vm = app.mount('#app')

vm.a = 3 // => new: 3, old: 1

注意,*不应该使用箭头函数来定义 watcher 函数* (例如 searchQuery: newValue => this.updateAutocomplete(newValue))。

理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向组件实例,this.updateAutocomplete 将是 undefined。

VUE 过度

Vue 提供了一些抽象概念,可以帮助处理过渡和动画,特别是在响应某些变化时。这些抽象的概念包括:

  • 在 CSS 和 JS 中,使用内置的 <transition> 组件来钩住组件中进入和离开 DOM。
  • 在处理多个元素位置更新时,使用 <transition-group> 组件,通过 FLIP 技术来提高性能。

自定义动画过渡

尽管 <transition> 组件对于组件的进入和离开非常有用,但你也可以通过添加一个条件 class 来激活动画,而无需挂载组件。

TransformOpacity更改 transform 不会触发任何几何形状变化或绘制。这意味着该操作可能是由合成器线程在 GPU 的帮助下执行。

因此,他们是在 web 上做元素移动的理想选择。

诸如 perspectivebackface-visibilitytransform:translateZ(x) 等 property 将让浏览器知道你需要硬件加速。

如果要对一个元素进行硬件加速,可以应用以下任何一个 property (并不是需要全部,任意一个就可以):

1
2
3
perspective: 1000px;
backface-visibility: hidden;
transform: translateZ(0);

案例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
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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.shake {
animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
}

@keyframes shake {
10%,
90% {
transform: translate3d(-1px, 0, 0);
}

20%,
80% {
transform: translate3d(2px, 0, 0);
}

30%,
50%,
70% {
transform: translate3d(-4px, 0, 0);
}

40%,
60% {
transform: translate3d(4px, 0, 0);
}

}
</style>

<body>
<div id="app">
<div :class="{ shake: noActivated }">
<button @click="noActivated = true">Click me</button>
<span v-if="noActivated">Oh no!</span>
</div>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
noActivated: false
}
},
});

const vm = app.mount("#app");
</script>

</html>

当页面上的数据发生变化的时候也会触发过度效果

案例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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.movearea {
transition: 0.2s background-color ease;
}

</style>

<body>
<div id="app">
<div @mousemove="xCoordinate" :style="{ backgroundColor: hsl(${x}, 100%, 50%) }" class="movearea">
<h3>鼠标移动上来</h3>
<p>x: {{x}}</p>
</div>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
x: 0
}
},
methods: {
xCoordinate(e) {
this.x = e.clientX
}
},
});

const vm = app.mount("#app");
</script>

</html>

单元素过渡

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

案例11

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.movearea {
transition: 0.2s background-color ease;
}

</style>

<body>
<div id="app">
<button @click="show = !show">切换显示</button>

<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
show: false
}
},
});

const vm = app.mount("#app");
</script>

</html>

动画相关的 class

在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。
  4. v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被删除),在过渡/动画完成之后移除。

Transition Diagram

对于这些在过渡中切换的类名来说,如果你使用一个没有名字的 <transition>,则 v- 是这些class名的默认前缀。如果你使用了 <transition name="my-transition">,那么 v-enter-from会替换为 my-transition-enter-from

v-enter-activev-leave-active 可以控制进入/离开过渡的不同的缓和曲线

案例12

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>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
/* 可以设置不同的进入和离开动画 /
/
设置持续时间和动画函数 */

.fade-enter-active {
transition: all 0.3s ease-out;
}

.fade-leave-active {
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.fade-enter-from,
.fade-leave-to {
transform: translateX(50px);
opacity: 0;
}

</style>

<body>
<div id="app">
<button @click="show = !show">切换显示</button>

<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
show: false
}
},
});

const vm = app.mount("#app");
</script>

</html>

多按钮切换状态

原理:

使用多个 v-if 的多个元素的过渡可以重写为绑定了动态属性的单个元素过渡。

案例13

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.v-enter-active {
animation: bounce-in 0.5s;
}

.v-leave-active {
animation: bounce-in 0.5s reverse;
}

@keyframes bounce-in {
0% {
transform: scale(0);
}

50% {
transform: scale(1.25);
}

100% {
transform: scale(1);
}

}
</style>

<body>
<div id="app">
<transition>
<button v-if="docState == 0" key="0" @click="change">
已保存
</button>
<button v-else-if="docState == 1" key="1" @click="change">
编辑完成
</button>
<button v-else-if="docState == 2" key="2" @click="change">
编辑中
</button>
</transition>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
docState: 1
}
},
methods: {
change() {
this.docState = ++this.docState % 3
console.log(this.docState)
}
}
});

const vm = app.mount("#app");
</script>

</html>

也可以写为:

案例14

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.v-enter-active {
animation: bounce-in 0.5s;
}

.v-leave-active {
animation: bounce-in 0.5s reverse;
}

@keyframes bounce-in {
0% {
transform: scale(0);
}

50% {
transform: scale(1.25);
}

100% {
transform: scale(1);
}

}
</style>

<body>
<div id="app">
<transition>
<button :key="docState" @click="change">
{{ buttonMessage }}
</button>
</transition>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
docState: 1
}
},
computed: {
buttonMessage() {
switch (this.docState) {
case 0:
return '已保存'
case 1:
return '编辑完成'
case 2:
return '编辑中'
}
}
},
methods: {
change() {
this.docState = ++this.docState % 3
console.log(this.docState)
}
}
});

const vm = app.mount("#app");
</script>

</html>

列表过渡

普通过度的特征:

  • 单个节点
  • 多个节点,每次只渲染一个

那么怎么同时渲染整个列表,比如使用 v-for?在这种场景下,我们会使用 <transition-group> 组件。

<transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。

要使用这个新功能只需了解新增的 v-move,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name attribute 来自定义前缀,也可以通过 move-class attribute 手动设置。

案例15

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.v-move {
transition: transform 1s;
-webkit-transition: -webkit-transform 1s;
}

</style>

<body>
<div id="app">
<button @click="add">Add</button>
<button @click="remove">Remove</button>
<transition-group class="list" tag="p">
<span v-for="item in items" :key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
}
},
methods: {
randomIndex() {
return Math.floor(Math.random() * this.items.length)
},
add() {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove() {
this.items.splice(this.randomIndex(), 1)
}
}
});

const vm = app.mount("#app");
</script>

</html>

动画效果也可以配合各种插件一起使用

如 animate.css等

案例16

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
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8" />
<title></title>
<link rel="stylesheet" type="text/css" href="css/animate.min.css" />
<script src="../js/vue.min.js" type="text/javascript" charset="utf-8"></script>
</head>
<style>
.v-move {
transition: transform 1s;
-webkit-transition: -webkit-transform 1s;
}

</style>

<body>
<div id="app">
<button v-on:click="add">添加</button>
<button v-on:click="remove">删除</button>
<button v-on:click="shuffle">乱序</button>
<transition-group>
<div v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</div>
</transition-group>
</div>
</body>
<script type="text/javascript">
const app = Vue.createApp({
data() {
return {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
}
},
methods: {
randomIndex: function() {
return Math.floor(Math.random() * this.items.length)
},
add: function() {
//splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目
//index 必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
//howmany 必需。要删除的项目数量。如果设置为 0,则不会删除项目。
//item1, ..., itemX 可选。向数组添加的新项目。
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function() {
this.items.splice(this.randomIndex(), 1)
},
shuffle: function() {
this.items.sort(function() {
return 0.5 - Math.random();
});
}
}
});

const vm = app.mount("#app");
</script>

</html>

作业

实现列表切换效果

image-20210924154234802

使用vue实现如下效果

image-20210926094025211

使用vue实现tab切换页

第一页

image-20210926094048175

第二页

image-20210926094057687

第三页

image-20210926094106254

实现如下购物车自动计算效果

image-20210926094117832

posted @ 2021-11-20 16:05  柠檬色的橘猫  阅读(185)  评论(0)    收藏  举报