第3章Vue事件、组件及生命周期
Vue 
中有很多Vue 
WebUI 
组件库可供开发者使用,那么组件是如何开发出来的? 针
对组件的事件处理又是如何描述的? 本章将对Vue 
基础知识进行讲解,内容包括Vue 
事
件处理、Vue 
组件、Vue 
生命周期等。


● 掌握Vue 
的事件监听操作
● 掌握Vue 
组件的定义和注册方法
● 掌握Vue 
组件直接传递数据的方法
● 掌握Vue 
生命周期钩子函数的使用方法
人生总要经历起起伏伏,不要因为一两次的失败就郁郁寡欢。打磨自己的过程总是
充满了艰难和迷茫,要相信:坚持的人,一定能找到属于自己的亮光。


69 
3.1 Vue事件
可以使用v-on指令(通常缩写为符号@)监听DOM 事件,并在触发事件时执行一些
JavaScript。用法为“v-on:事件名="方法"”或使用快捷方式“@事件名="方法"”。之前
的案例使用过@click、@keyup.enter等,下面详细介绍这些内容。
3.1.1 事件监听
在Vue中,可以使用内置指令v-on监听DOM 事件,下面通过例3-1进行演示。
【例3-1】 事件监听。
(1)创建文件夹chapter03,然后在该目录下创建demo01.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>Title</title> 
6 </head> 
7 <body> 
8 <div id="app"> 
9 <div>{{count}}</div> 
10 <button type="button" @click="count+=1">+1</button> 
11 <p> 
12 <button type="button" @click="showDt">当前日期时间</button> 
13 {{now}} 
14 </p> 
15 </div> 
16 <script src="vue.js"></script> 
17 <script> 
18 const app = Vue.createApp( 
19 { 
20 data() { 
21 return { 
22 count: 0, 
23 now: '' 
24 } 
25 } 
26 methods: { 
27 showDt: function () { 
28 this.now = new Date() 
29 } 
30 }

70 
31 }) 
32 app.mount('#app') 
33 </script> 
34 </body> 
35 </html> 
(2)在浏览器中打开demo01.html文件,运行结果如图3-1所示。单击按钮后,运行
结果如图3-2所示。
图3-1 初始结果
图3-2 单击按钮后的运行结果
3.1.2 事件修饰符
事件修饰符是自定义事件行为,配合v-on指令使用,写在事件之后,用“.”符号连接, 
如v-on:click.stop表示阻止事件冒泡。
示例如下: 
1 <!--阻止单击事件冒泡--> 
2 <a v-on:click.stop="doSth"></a> 
3 <!--阻止事件默认行为--> 
4 <form v-on:submit.prevent="onSubmit"></form> 
5 <!--修饰符串联--> 
6 <a v-on:click.stop.prevent="doSth"></a> 
7 <!--只有修饰符--> 
8 <form v-on:submit.prevent></form> 
9 <!--添加事件监听器时使用事件捕获模式-->

71 
10 <a v-on:click.capture="doSth"></a> 
11 <!--只当事件在该元素本身触发时触发回调--> 
12 <div v-on:click.self="doSth"></div> 
13 <!--事件只触发一次--> 
14 <a v-on:click.once="doSth"></a> 
3.1.3 按键修饰符
在监听键盘事件时,经常需要检查常见的键值。为了方便开发,Vue允许为v-on添
加按键修饰符以监听按键,如Enter、Space、Shift和Down等。下面以Enter键为例进行
演示。
【例3-2】 按键修饰符的使用。
(1)创建chapter03/demo02.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>标题</title> 
6 </head> 
7 <body> 
8 <div id="app"> 
9 输入后按Enter 键则提交: <input type="text" v-on:keyup.enter="submit"> 
10 </div> 
11 <script src="vue.js"></script> 
12 <script> 
13 const app = Vue.createApp( 
14 { 
15 methods: { 
16 submit() { 
17 console.log('表单提交') 
18 } 
19 } 
20 }) 
21 app.mount('#app') 
22 </script> 
23 </body> 
24 </html> 
上述代码中,当按Enter键后,就会触发submit()事件处理方法。
(2)在浏览器中打开demo02.html,单击input输入框使其获得焦点,然后按Enter 
键,运行结果如图3-3所示。从图3-3中可以看出,控制台输出了“表单提交”,说明键盘事
件绑定成功且执行。

72 
图3-3 按Enter键触发事件 
3.2 Vue组件
Vue可以进行组件化开发,组件是Vue的基本结构单元,在开发过程中使用起来非
常方便灵活,只需要按照Vue规范定义组件,将组件渲染到页面即可。组件能实现复杂
的页面结构,提高代码的可复用性。在开源社区,有很多VueWebUI组件库可供开发者
使用。例如,ElementUI就是一套基于Vue.js的高质量UI组件库,可以用其快捷地开发
前端界面。下面对Vue组件进行讲解。
3.2.1 什么是组件
在Vue中,组件是构成页面中独立结构的单元,能够减少代码的重复编写,提高开发
效率,降低代码之间的耦合度,使项目更易维护和管理。组件主要以页面结构的形式存
在,不同组件也具有基本的交互功能,可以根据业务逻辑实现复杂的项目功能。
下面通过一个案例演示组件的定义和使用。
【例3-3】 组件的定义和使用。
(1)创建chapter03/demo03.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>创建并注册组件</title> 
6 </head> 
7 <body> 
8 <div id="app"> 
9 <my-com></my-com> 
10 <hr/> 
11 <my-com></my-com> 
12 </div> 
13 <script src="vue.js"></script>

73 
14 <script> 
15 const app = Vue.createApp({}) 
16 app.component('myCom', { 
17 template: '<button type="button" 
18 @click="btnHandler">{{msg}}</button>',data() { 
19 return { 
20 msg: '自定义组件' 
21 } 
22 }, 
23 methods: { 
24 btnHandler() { 
25 alert('haha~~'); 
26 } 
27 } 
28 }); 
29 app.mount('#app') 
30 </script> 
31 </body> 
32 </html> 
在上述代码中,第16行的app.component()表示注册组件的API,参数myCom 为组
件名称,该名称与页面中的<my-com>标签名对应;第17行的template表示组件的模
板;第18~22行表示组件中的数据,它必须是一个函数,并通过返回值返回初始数据;第
23~28行表示组件中的方法。
(2)在浏览器中打开demo03.html,运行结果如图3-4所示。
图3-4 自定义组件的运行结果
如图3-4所示,一共有两个my-comp组件,单击某一个组件时,会显示一个弹框。
通过例3-3可以看出,利用Vue的组件功能可以非常方便地复用页面代码,实现一次
定义、多次使用的效果。
3.2.2 局部注册组件
前面学习的app.component()方法用于全局注册组件,除了全局注册组件外,还可以

74 
局部注册组件,即通过Vue实例的component属性实现。下面通过例3-4进行演示。
【例3-4】 局部注册组件。
(1)创建chapter03/demo04.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>局部组件</title> 
6 </head> 
7 <body> 
8 <div id="app"> 
9 <my-com></my-com> 
10 <hr/> 
11 <my-com2></my-com2> 
12 </div> 
13 <template id="tem"> 
14 <div> 
15 <p>局部组件2 --{{count}}</p> 
16 <button type="button" @click="btnHandler">单击</button> 
17 </div> 
18 </template> 
19 <script src="vue.js"></script> 
20 <script> 
21 const app = Vue.createApp({}) 
22 //定义一个普通的JavaScript 对象
23 const Com = { 
24 template: '<h3>局部组件-<input v-model="msg">-{{msg}}</h3>', 
25 data() { 
26 return {msg: 'hello'} 
27 } 
28 } 
29 const Com2 = { 
30 template: '#tem', 
31 data() { 
32 return { 
33 count: 0 
34 } 
35 }, 
36 methods: { 
37 btnHandler: function () { 
38 this.count++; 
39 } 
40 } 
41 }

75 
42 app.component('myCom', Com) 
43 .component('myCom2', Com2) 
44 app.mount('#app')</script> 
45 </body> 
46 </html> 
在上述代码中,第42、43行的component表示组件配置选项,注册组件时,只需要在
component内部定义组件即可。
(2)在浏览器中打开demo04.html,运行结果如图3-5所示。
图3-5 components注册组件的运行结果
在上述代码中,可以看到template模板是使用字符串保存的,这种方式不仅容易出
错,也不适合编写复杂的页面结构。实际上,模板代码是可以写在HTML结构中的,Vue 
提供了<template>标签以定义结构的模板,可以在该标签中书写HTML代码,然后通
过id值绑定到组件内的template属性上,例如代码第14~19行定义了模板HTML代
码,第32行将该模板<template>绑定到了组件上。
3.2.3 组件之间的数据传递
在Vue中,组件实例具有局部作用域,组件之间的数据传递需要借助一些工具(如
props属性)以实现从父组件向子组件传递数据信息。从父组件向子组件传递数据信息是
从外部向内部传递,从子组件向父组件传递数据信息是从内部向外部传递。
在Vue中,数据传递主要通过props属性和$emit方式实现,下面分别进行讲解。
1.props传值
props即道具。组件实例的作用域是孤立的,这意味着不能且不应在子组件的模板内
直接引用父组件的数据。通常可以使用props把数据传给子组件。下面具体演示props 
属性的使用。
【例3-5】 props属性的使用。
(1)创建chapter03/demo05.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en">

76 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>props</title> 
6 </head> 
7 <body> 
8 <div id="app"> 
9 <p>父组件title: {{title}} ---num: 
10 <input v-model="num"></p> 
11 <hr/> 
12 <son1 v-bind:son1-title="title" v-bind:son1-num="num"></son1> 
13 <hr/> 
14 <son2:title="title":num="num":obj="user":arr="msg"></son2> 
15 </div> 
16 <!--子组件利用props 声明属性,父组件加载子组件时为属性赋值--> 
17 <!--子组件模板--> 
18 <template id="son2"> 
19 <div> 
20 <p>我是子组件2,以下演示来自父组件数据:</p> 
21 <p>{{title}}--{{num * 2}} --{{obj}} --{{obj.name}} --{{arr}}</p> 
22 </div> 
23 </template> 
24 <script src="vue.js"></script> 
25 <script> 
26 const app = Vue.createApp({ 
27 data() { 
28 return { 
title: '使用props 父组件向子组件传参', 
29 num: 3, 
30 msg: ['props', 'emit', 'bind', 10], 
31 user: { 
32 id: 1, 
33 name: 'props' 
34 } 
35 } 
36 } 
37 }) 
38 //定义子组件
39 const son1 = { 
40 template: '<div>来自父组件title 与num: {{son1Title}}: 
41 {{son1Num}}</div>', 
42 props: ['son1Title', 'son1Num'] 
43 }; 
44 const son2 = {

77 
45 template: '#son2', 
46 props: { 
47 title: String, 
48 num: { 
49 type: Number, 
50 required: true, 
51 default: 11, 
52 validator: function (value) { 
53 return value > 0; 
54 } 
55 }, 
56 obj: { 
57 type: Object, 
58 default: function () { 
59 return { 
60 id: 1, name: 'admin' 
61 } 
62 } 
63 }, 
64 arr: { 
65 type: Array, 
66 default: function () { 
67 return ['apple', 'banana'] 
68 } 
69 } 
70 } 
71 }; 
72 app.component('son1', son1) 
73 .component('son2', son2) 
74 app.mount("#app") 
75 </script> 
76 </body> 
77 </html> 
上述代码声明了两个子组件son1与son2,其中,son1子组件为了使用父组件的数
据,必须先定义props属性,即第42行“props:['son1Title','son1Num']”,此处仅仅是声
明两个属性,没有对属性使用任何约束,在第12行中,使用v-bind将父组件数据通过已定
义的props属性传递给了子组件。需要注意的是,在子组件中定义props时,使用了
CamelCase命名法。由于HTML 不区分大小写,因此当camelCase的props用于特性
时,需要将其转为kebab-case(连字符隔开)。例如,在props中定义的myName在用作特
性时,需要将其转换为my-name。在父组件中使用子组件时,可以通过以下语法将数据
传递给子组件: 
1 <子组件v-bind:子组件属性=父组件数据属性></子组件>

78
可以为组件的props指定验证要求。如果有一个要求没有被满足,则Vue会在浏览
器控制台发出警告。为了定制props的验证方式,可以为props中的值提供一个带有验
证要求的对象,而不是一个字符串数组,如上述代码中定义的子组件son2,son2中定义的
props属性为第33~57行;其中,属性num通过type定义了类型,通过reqietue要

urd:r
求必须为该属性赋值,default定义了默认值,validator定义了验证要求;属性obj和ar 
各自定义了类型和默认值。

需要注意的是,当为对象和数组定义默认值时,必须使用函数返回,如上述代码的
第58~63行和第66~68行所示。

(2)在浏览器中打开demo05.tml,运行结果如图36所示。
h


图3-
6 
props传值的运行结果(1) 

在图3-6所示的页面中,子组件显示标题为“使用props父组件向子组件传参”以及数
字3,说明父组件信息已经传递到子组件。当更新父组件num的值时,子组件中的数据也
会随之发生改变,如图3-7所示。


图3-
7 
props传值的运行结果(2) 

需要注意的是,props是以从上到下的单向数据流传递的,且父级组件的props更新
会向下流动到子组件中,但是反过来则不行,这是为了防止子组件无意中修改父组件的
状态。


79 
props的type可以是下列原生构造函数中的一个: 
● String 
● Number 
● Boolean 
● Array 
● Object 
● Date 
● Function 
● Symbol 
此外,type还可以是一个自定义的构造函数。
2.$emit传值
$emit能够将子组件中的值传递到父组件中。$emit可以触发父组件中定义的事
件,子组件的数据信息通过传递参数的方式完成。下面通过例3-6进行代码演示。
【例3-6】 $emit传值的使用。
(1)创建chapter03/dem06.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>props</title> 
6 <script src="vue.js"></script> 
7 </head> 
8 <body> 
9 <div id="app"> 
10 <p>我是父组件--来自子组件的数据为: <br/>{{fromSon}}</p> 
11 <hr/> 
12 <son @son-msg="getDataFromSon"></son> 
13 </div> 
14 <template id="son"> 
15 <div> 
16 <p>我是子组件</p> 
17 <input type="text" v-model="msg"/>--{{msg}} 
18 <button type="button" @click="toParent">将数据传递到父组件
19 </button> 
</div> 
20 </template> 
21 <script> 
22 const app = Vue.createApp({ 
23 data() { 
24 return {

80 
25 fromSon: '' 
26 } 
27 }, 
28 methods: { 
29 getDataFromSon: function (sonData) { 
30 this.fromSon = sonData; 
31 } 
32 } 
33 }) 
34 const son = { 
35 template: '#son', 
36 data() { 
37 return { 
38 msg: '子组件字符串' 
39 } 
40 }, 
41 methods: { 
42 toParent() { 
43 this.$emit('son-msg', this.msg); 
44 } 
45 } 
46 } 
47 app.component('son', son) 
48 .mount("#app") 
49 </script> 
50 </body> 
51 </html> 
上述代码的第12行,即在父组件中调用子组件时,绑定了一个自定义事件和对应的
处理函数@son-msg="getDataFromSon";在第43行,子组件把要发送的数据通过触发
自定义事件传递给父组件this.$emit(s' on-msg',this.msg);其中,$emit()的意思是把事
件沿着作用域链向上派送。
(2)在浏览器中打开demo06.html文件,运行结果如图3-8所示。单击【将数据传递
到父组件】按钮,运行结果如图3-9所示。
图3-8 初始页面

81 
图3-9 传值成功
如图3-8所示,单击【将数据传递到父组件】按钮后,页面中显示了“子组件字符串”, 
说明成功完成了子组件向父组件的传值。
3.2.4 组件切换
Vue中的页面结构是由组件构成的,不同组件可以表示不同页面,适合进行单页应用
开发。下面通过例3-7演示登录组件和注册组件的切换。
【例3-7】 组件切换。
(1)创建chapter03/demo07.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>组件切换</title> 
6 <script src="vue.js"></script> 
7 </head> 
8 <body> 
9 <div id="app"> 
10 <a href="" @click.prevent="flag=true">登录</a> 
11 <a href="" @click.prevent="flag=false">注册</a> 
12 <login v-if="flag"></login> 
13 <register v-else="flag"></register> 
14 </div> 
15 <script> 
16 const app = Vue.createApp({ 
17 data() { 
18 return { 
19 flag: true 
20 } 
21 } 
22 })

82 
23 app.component('login', { 
24 template: '<h3>登录账号</h3>' 
25 }).component('register', { 
26 template: '<h3>注册账号</h3>' 
27 }).mount("#app")</script> 
28 </body> 
29 </html> 
上述代码中,第12行的login表示登录组件,第13行的register表示注册组件;第12 
行的v-if指令值为true,表示加载当前组件,否则移除当前组件;第10、11行的.prevent事
件修饰符用于阻止<a>标签的超链接默认行为。
(2)在浏览器中打开demo07.html文件,运行结果如图3-10所示。在页面中单击
“注册”链接后,运行结果如图3-11所示。
图3-10 初始页面
图3-11 注册页面
从例3-7可以看出,组件的切换是通过v-if控制的。除了这种方式外,还可以通过组
件的is属性实现,即使用is属性匹配组件的名称,下面通过例3-8进行演示。
【例3-8】 is属性的使用。
(1)创建chapter03/demo08.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>组件切换</title> 
6 <script src="vue.js"></script>

83 
7 </head> 
8 <body> 
9 <div id="app"> 
10 <a href="" @click.prevent="comName='login'">登录</a> 
11 <a href="" @click.prevent="comName='register'">注册</a> 
12 <component v-bind:is="comName"></component> 
13 </div> 
14 <script> 
15 const app = Vue.createApp({ 
16 data() { 
17 return { 
18 comName: 'login' 
19 } 
20 } 
21 }) 
22 app.component('login', { 
23 template: '<h3>登录组件</h3>' 
24 }).component('register', { 
25 template: '<h3>注册组件</h3>' 
26 }).mount("#app") 
27 </script> 
28 </body> 
29 </html> 
在上述代码中,第12行的is属性值绑定了data中的comName;第10、11行的<a> 
标签用来修改comName的值,从而切换对应的组件。
(2)在浏览器中打开demo08.html文件,运行结果与图3-10所示相同。 
3.3 Vue生命周期
Vue实例为生命周期提供了回调函数,用来在特定的情况下触发,贯穿了Vue实例
化的整个过程,这给用户在不同阶段添加自己的代码提供了机会。每个Vue实例在被创
建时都要经过一系列的初始化过程,如初始数据监听、编译模板、将实例挂载到DOM、在
数据变化时更新DOM 等。
Vue的生命周期分为4个阶段,涉及7个函数。
● create创建:setup()。
● mount挂载(把视图和模型关联起来):onBeforeMount(),onMounted()。
● update更新(模型的更新对视图造成何种影响):onBeforeUpdate(),onUpdated()。
● unMount销毁(视图与模型失去联系):onBeforeUnmount(),onUnmounted()。
3.3.1 钩子函数
钩子函数用来描述Vue实例从创建到销毁的整个生命周期,具体如表3-1所示。

84 
表3-1 生命周期钩子函数
钩 子说 明
setup 开始创建组件之前,在beforeCreate和created之前执行,创建的是data和method 
onBeforeMount 组件挂载到节点上之前执行的函数
onMounted 组件挂载完成后执行的函数
onBeforeUpdate 组件更新之前执行的函数
onUpdated 组件更新完成之后执行的函数
onBeforeUnmount 组件卸载之前执行的函数
onUnmounted 组件卸载完成后执行的函数 
下面对这些钩子函数分别进行讲解。
3.3.2 实例创建
setup():beforeCreate和created 与setup 几乎是同时进行的,所以可以把写在
beforeCreate和created这两个周期的代码直接写在setup中。
【例3-9】 实例创建。
(1)创建chapter03/demo09.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>钩子函数</title> 
6 <script src="vue.js"></script> 
7 </head> 
8 <body> 
9 <div id="app"> 
10 <input v-model.lazy="msg"/> 
11 <button type="button" @click="btnHandler">{{msg}}</button> 
12 </div> 
13 <script> 
14 const app = Vue.createApp({ 
15 data() { 
16 return { 
17 msg: 'helloworld', 
18 } 
19 }, 
20 methods: { 
21 btnHandler: function () { 
22 console.log('button click'); 
23 } 
24 },

85 
25 setup() { 
26 console.log('setup()-----'); 
27 console.log(this.$el); //undefined 
28 console.log(this.$data); //undefined 
29 console.log(this.msg); //undefined 
30 alert('setup'); 
31 }, 
32 }) 
33 app.mount("#app") 
34 </script> 
35 </body> 
36 </html> 
(2)在浏览器中打开demo09.html文件,运行结果如图3-12所示。
图3-12 setup的运行结果
如图3-12所示,setup钩子函数输出msg时为undefined,这是因为此时数据还没有
被监听,同时页面上没有挂载对象。
3.3.3 页面挂载
onBeforeMount()表示模板已经在内存中编辑完成了,但是尚未把模板渲染到页
面中。
onMounted()在这时挂载完毕,此时DOM 节点已被渲染到文档内,一些需要DOM 
的操作在此时才能正常进行(常在此方法中进行ajax请求数据,渲染到DOM 节点)。
【例3-10】 页面挂载。
(1)创建chapter03/demo10.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en">

86 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>钩子函数</title> 
6 <script src="vue.js"></script> 
7 </head> 
8 <body> 
9 <div id="app"> 
10 <input v-model.lazy="msg"/> 
11 <button type="button" @click="btnHandler">{{msg}}</button> 
12 </div> 
13 <script> 
14 const {onMounted, onBeforeMount, reactive,toRefs} = Vue 
15 const app = Vue.createApp({ 
16 setup() { 
17 const data = reactive({ 
18 msg: 'helloworld', 
19 }) 
20 const methods = { 
21 btnHandler: function () { 
22 console.log('button click'); 
23 }, 
24 } 
25 onBeforeMount(() =>{ 
console.log('beforeMount()----'); 
26 let btn = document.querySelector('button') 
27 console.log(btn) 
28 }) 
29 onMounted(() =>{ 
30 console.log('mounted()----'); 
31 let btn = document.querySelector('button') 
32 console.log(btn) //此时可以打印出button 的值 
}) 
33 return { 
34 ...toRefs(data), 
35 ...methods 
36 } 
37 } 
38 }) 
39 app.mount("#app") 
40 </script> 
41 </body> 
42 </html> 
(2)在浏览器中打开demo10.html文件,运行结果如图3-13所示。

87 
图3-13 onBeforeMount与onMounted的运行结果
从图3-13可以看出,在挂载之前,数据并没有被关联到对象上,所以页面无法展示页
面数据;在挂载之后就获得了msg数据,并通过插值语法展示到页面中。
3.3.4 数据更新
onBeforeUpdate():当执行beforeUpdate时,页面中显示的数据还是旧的,此时data 
数据是最新的,页面尚未和最新的数据保持同步。
onUpdated():页面和data数据已经保持同步,都是最新的。
【例3-11】 数据更新。
(1)创建chapter03/demo11.html文件,具体代码如下: 
1 <!DOCTYPE html> 
2 <html lang="en"> 
3 <head> 
4 <meta charset="UTF-8"> 
5 <title>钩子函数</title> 
6 <script src="vue.js"></script> 
7 </head> 
8 <body> 
9 <div id="app"> 
10 <div v-if="isShow" ref="test">test</div> 
11 <button @click="isShow=!isShow">更新</button> 
12 </div> 
13 <script> 
14 const {onBeforeUpdate, onUpdated, ref} = Vue 
15 const app = Vue.createApp({ 
16 setup() { 
17 const test = ref()