Vue

Vue简介

渐进式JavaScript框架,易学易用,性能出色,使用场景丰富的web前端框架

Vue(发音为/vju:/,类似view)是一款用于构建用户界面的JavaScript框架。基于标椎HTML、CSS和JavaScript构建,提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue都能胜任

渐进式框架

Vue 是一个框架,也是一个生态。其功能覆盖了大部分前端开发常见的需求。但 Web 世界是十分多样化的。不同的开发者在 Web 上构建的东西可能在形式和规模上会有很大的不同。考虑到这一点,Vue 的设计非常注重灵活性和“可以被逐步集成”这个特点。根据你的需求场景,你可以用不同的方式使用 Vue:

  • 无需构建步骤,渐进式增强静态的HTML
  • 在任何页面中作为Web Components嵌入
  • 单页应用(SPA)(整个项目在一个页面)
  • 全栈/服务端渲染(SSR)
  • JamStack/静态站点生成(SSG)
  • 开发桌面端、移动端、WebGL,甚至是命令行终端中的界面

API风格

选项式API

Options API

使用选项式API,我们可以用包含多个选项的对象来描述组件的逻辑,例如datamethodsmounted。选项所定义的属性都会暴露在函数内部this上,会指向当前的组件实例

例如实现一个按钮累加的功能

image-20241206094058037

组合式API

Composition API

通过组合式API,可以使用导入的API函数来描述组件逻辑

image-20241206094221033

该选哪一个?

两种 API风格都能够覆盖大部分的应用场景。它们只是同一个底层系统所提供的两套不同的接口。实际上,选项式 API是在组合式 API 的基础上实现的!关于 Vue 的基础概念和知识在它们之间都是通用的。

生产项目中

  • 当你不需要使用构建工具,或者打算主要在低复杂度的场景中使用 Vue,例如渐进增强的应用场景,推荐
    采用选项式 API
  • 当你打算用 Vue 构建完整的单页应用,推荐采用组合式 API+ 单文件组件

目录结构

image-20241206101118170

  • node_moudles — vue项目的运行依赖文件夹
  • public — 资源文件夹(浏览器图标..)
  • src — 源码文件夹
  • .gitignore — git忽略文件
  • index.html — 入口html文件
  • package.json — 信息描述文件
  • README.md — 注释文件
  • vite.config.js — Vue配置文件

vite构建

vite是新一代前端构建工具

  • 轻量快速的热重载(HMR),能实现极速的服务启动
  • TypeScriptJSXCSS 等支持开箱即用
  • 真正的按需编译,不再等待整个应用编译完成

具体操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 1.创建命令
npm create vue@latest

## 2.具体配置
## 配置项目名称
√ Project name: vue3_test
## 是否添加TypeScript支持
√ Add TypeScript? Yes
## 是否添加JSX支持
√ Add JSX Support? No
## 是否添加路由环境
√ Add Vue Router for Single Page Application development? No
## 是否添加pinia环境
√ Add Pinia for state management? No
## 是否添加单元测试
√ Add Vitest for Unit Testing? No
## 是否添加端到端测试方案
√ Add an End-to-End Testing Solution? » No
## 是否添加ESLint语法检查
√ Add ESLint for code quality? Yes
## 是否添加Prettiert代码格式化
√ Add Prettier for code formatting? No

核心语法

setup

setupVue3中一个新的配置项,值是一个函数,它是 Composition API “表演的舞台,组件中所用到的:数据、方法、计算属性、监视…等等,均配置在setup

特点

  • setup函数返回的对象中的内容,可直接在模板中使用。
  • setup中访问thisundefined
  • setup函数会在beforeCreate之前调用,它是“领先”所有钩子执行的

代码示例:

注意这里由于安装了eslint插件,校验代码格式和规范,不按照它的来会直接报错。我在此之后关闭了校验。(这里是因为name用的person,不是eslint插件规定的多个单词驼峰命名—坑)

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
<template>
<div class="person">
<h2>姓名: {{name}}</h2>
<h2>年龄: {{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">年龄+1</button>
<button @click="showTel">点我查看联系方式</button>

</div>
</template>

<script>
export default {
name: 'myPerson',
setup() {
let name = '张三'
let age = 18
let tel = '13888888888'
function changeName() {
name = 'zhang-san'
console.log(name)
}

function changeAge() {
age += 1
console.log(age)
}

function showTel() {
alert(tel)
}
return {name, age, tel, changeName, changeAge, showTel}
}
}


</script>

返回值

  • 若返回一个对象:则对象中的:属性、方法等,在模板中均可以直接使用(重点关注)

  • 若返回一个函数:则可以自定义渲染内容,代码如下:

    1
    2
    3
    setup(){
    return ()=> '你好啊!'
    }

setup与OptionsAPI的关系

  • Vue2 的配置(datamethos…)中可以访问到 setup中的属性、方法。
  • 但在setup不能访问到Vue2的配置(datamethos…)。
  • 如果与Vue2冲突,则setup优先。

setup语法糖

setup函数有一个语法糖,这个语法糖,可以让我们把setup独立出去,代码如下

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="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changAge">年龄+1</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>

<script>
export default {
name:'Person'
}
</script>
<script setup>
console.log(this)
let name = '张三'
let age = 18
let tel = '13888888888'
function changeName() {
name='李四'
}
function changAge(){
console.log(age)
age += 1
}
function showTel(){
alert(tel)
}
</script>

扩展:上述代码还需要编写一个不写setup的script标签,去指定组件名字,比较麻烦,我们可以借助vite中的插件简化

  1. npm i vite-plugin-vue-setup-extend -D

  2. vite.config.ts 中

    1
    2
    3
    4
    5
    6
    import { defineConfig } from 'vite'
    import VueSetupExtend from 'vite-plugin-vue-setup-extend'

    export default defineConfig({
    plugins: [ VueSetupExtend() ]
    })
  3. 设置为script setup lang=”ts” name=”Person”

ref创建:基本类型

ref基本类型的响应式数据

  • 作用:定义响应式变量
  • 语法:let xxx = ref(初始值)
  • 返回值:一个RefImpl的实例对象,简称ref对像或ref,ref对象的value属性是响应式的
  • 注意点:
    • ts中操作数据需要:xxx.value,但模板中不需要.value,直接使用即可
    • 何时需要.value?模板中不需要;包裹在响应式对象里面的ref不需要;未包裹的ref需要
    • 对于let name = ref(‘张三’)来说,name不是响应式的,name.value是响应式的

注意到此,作者JYH发现vite的好处,所以说重新用vite构建项目(目录中可以找到vite构建的步骤及好处)

ref创建代码如下

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="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changName">修改名字</button>
<button @click="changAge">年龄+1</button>
<button @click="showTel">点我查看联系方式</button>
</div>
</template>



<!-- 下面的写法是setup语法糖 -->
<script setup lang="ts" name="Person">
import {ref} from 'vue'
let name = ref('张三')
let age = ref(18)
//tel就是一个普通的字符串不是响应式的
let tel = '13888888888'

// 方法
function changName(){
name.value = '李四'
}
function changAge(){
console.log(age)
age.value += 1 //注意:此时这么修改age页面是不变化的
}
function showTel(){
alert(tel)
}
</script>

reactive创建:对象类型

对象类型的响应式数据

  • 作用:定义一个响应式对象(基本类型不要用它,要用ref,否则报错)
  • 语法:let 响应式对象=reactive(源对象)
  • 返回值:一个Proxy的实例对象,简称:响应式对象
  • 注意点:reactive定义的响应式数据是“深层次的”

代码:

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
<template>
<div class="person">
<h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2>
<h2>游戏列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
<h2>测试:{{ obj.a.b.c.d }}</h2>
<button @click="changeCarPrice">修改汽车价格</button>
<button @click="changeFirstGame">修改第一游戏</button>
<button @click="test">测试</button>
</div>
</template>


<script lang="ts" setup name="Person">
import { reactive } from 'vue';
let car = reactive({brand:'奔驰', price:100})
let games = reactive([
{id:'ahsgdyfa01', name: '英雄联盟'},
{id:'ahsgdyfa02', name: '王者荣耀'},
{id:'ahsgdyfa03', name: '原神!'},

])
let obj = reactive({
a:{
b:{
c:{
d:666
}
}
}
})
function changeCarPrice() {
car.price += 10;
}
function changeFirstGame() {
games[0].name = '流星蝴蝶剑'
}
function test() {
obj.a.b.c.d = 999
}

</script>

ref创建:对象类型

  • 其实ref接收的数据可以是:基本类型对象类型
  • ref接收的是对象类型,内部其实也是调用了reactive函数。
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
<template>
<div class="person">
<h2>汽车信息:一台{{ car.brand }}汽车,价值{{ car.price }}万</h2>
<h2>游戏列表:</h2>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
<h2>测试:{{obj.a.b.c.d}}</h2>
<button @click="changeCarPrice">修改汽车价格</button>
<button @click="changeFirstGame">修改第一游戏</button>
<button @click="test">测试</button>
</div>
</template>

<script lang="ts" setup name="Person">
import { ref } from 'vue'

// 数据
let car = ref({ brand: '奔驰', price: 100 })
let games = ref([
{ id: 'ahsgdyfa01', name: '英雄联盟' },
{ id: 'ahsgdyfa02', name: '王者荣耀' },
{ id: 'ahsgdyfa03', name: '原神' }
])
let obj = ref({
a:{
b:{
c:{
d:666
}
}
}
})

console.log(car)

function changeCarPrice() {
car.value.price += 10
}
function changeFirstGame() {
games.value[0].name = '流星蝴蝶剑'
}
function test(){
obj.value.a.b.c.d = 999
}
</script>

ref与reactive

  • 宏观角度
    1. ref定义:基本类型数据、对象类型数据
    2. reactive用来定义:对象类型数据
  • 区别
    1. ref创建的变量必须使用.value(可以使用volar插件自动添加.value
    2. reactive重新分配一个新对象,会失去响应式
  • 使用原则
    1. 若需要一个基本类型的响应式数据,必须使用ref
    2. 若需要一个响应式对象,层级不深,refreactive都可以
    3. 若需要一个响应式对象,且层级较深,推荐使用reactive

toRefs与toRef

  • 作用:将一个响应式对象中的每一个属性,转换为ref对象。并且改变解构的值,也会影响到原响应式对象的值。

  • 备注:toRefstoRef功能一致,但toRefs可以批量转换。

  • 语法如下:

    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
    <template>
    <div class="person">
    <h2>姓名:{{person.name}}</h2>
    <h2>年龄:{{person.age}}</h2>
    <h2>性别:{{person.gender}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="changeGender">修改性别</button>
    </div>
    </template>

    <script lang="ts" setup name="Person">
    import {ref, reactive, toRefs, toRef} from 'vue'
    let person = reactive({name:'张三', age:18, gender:'男'})
    // 通过toRefs将person对象中的n个属性批量取出,且依然保持响应式的能力
    //改变name和gender的值,也会影响到person里面的值
    let {name,gender} = toRefs(person)
    // 通过toRef将person对象中的gender属性取出,且依然保持响应式的能力
    let age = toRef(person,'age')
    function changeName() {
    name.value += '~'
    }
    function changeAge() {
    age.value += 1;
    }
    function changeGender() {
    gender.value = '女'
    }
    </script>

computed

根据已有数据计算出新的数据

  • 实现同样的功能,方法function没有缓存,模板调用几次,函数就执行几次;计算属性computed有缓存,模板调用多次,实际上只执行一次
    比如我要打印两次某个计算的结果function就要执行两次,而computed会执行第一次后缓存,只执行一次。

  • 计算属性实际上是一个ref响应式对象,因此赋值的时候要加上.value

    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
    <template>
    <div class="person">
    姓:<input type="text" v-model="firstName"> <br>
    名:<input type="text" v-model="lastName"> <br>
    全名:<span>{{fullName}}</span> <br>
    全名:<span>{{fullName}}</span> <br>
    <button @click="changeFullName">全名改为:li-si</button>
    </div>
    </template>

    <script setup lang="ts" name="App">
    import {ref,computed} from 'vue' //引入computed

    let firstName = ref('zhang')
    let lastName = ref('san')

    // 计算属性——只读取,不修改
    /* let fullName = computed(()=>{
    return firstName.value + '-' + lastName.value
    }) */


    // 计算属性——既读取又修改
    let fullName = computed({
    // 读取
    get(){
    return firstName.value + '-' + lastName.value
    },
    // 修改
    set(val){ 1
    console.log('有人修改了fullName',val)
    firstName.value = val.split('-')[0]
    lastName.value = val.split('-')[1]
    //第二种写法
    //const [str1,str2] = val.split('-')
    //firstName.value = str1
    //lastName.value = str2
    }
    })

    function changeFullName(){
    fullName.value = 'li-si' //引起set的val变化
    }
    </script>

watch

  • 作用:监视数据的变化
  • 特点:Vue3中的watch只能监视一下四种数据
    1. ref定义的数据
    2. reactive定义的数据
    3. 函数返回一个值(getter函数)
    4. 一个包含上述内容的数组

vue3中使用watch会有以下五种情况

情况一

监视ref定义的基本类型数据:直接写数据名即可,监视的是其value值的改变

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
<template>
<div class="person">
<h1>情况一:监视【ref】定义的【基本类型】数据</h1>
<h2>当前求和为:{{sum}}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>

<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum(){
sum.value += 1
}
// 监视,情况一:监视【ref】定义的【基本类型】数据
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
if(newValue >= 10){
stopWatch()
}
})
</script>

情况二

监视ref定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。

  • 若修改的是ref定义的对象中的属性,newValueoldValue 都是新值,因为它们是同一个对象。
  • 若修改整个ref定义的对象,newValue 是新值, oldValue 是旧值,因为不是同一个对象了。
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
<template>
<div class="person">
<h1>情况二:监视【ref】定义的【对象类型】数据</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
</div>
</template>

<script lang="ts" setup name="Person">
import {ref,watch} from 'vue'
// 数据
let person = ref({
name:'张三',
age:18
})
// 方法
function changeName(){
person.value.name += '~'
}
function changeAge(){
person.value.age += 1
}
function changePerson(){
person.value = {name:'李四',age:90}
}
/*
监视,情况一:监视【ref】定义的【对象类型】数据,监视的是对象的地址值,若想监视对象内部属性的变化,需要手动开启深度监视
watch的第一个参数是:被监视的数据
watch的第二个参数是:监视的回调
watch的第三个参数是:配置对象(deep、immediate等等.....)
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{deep:true,immediate:true})

</script>

情况三

监视reactive定义的【对象类型】数据,且默认开启了深度监视,且深层监视无法关闭。

无法监视地址值,因为对象地址值没有改变,本质上assign在原对象上进行的是赋值。

newValue和oldValue值相同,都是新值,还是因为对象地址值没有改变,本质上assign在原对象上进行的是赋值。

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
<template>
<div class="person">
<h1>情况三:监视【reactive】定义的【对象类型】数据</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changePerson">修改整个人</button>
<hr>
<h2>测试:{{obj.a.b.c}}</h2>
<button @click="test">修改obj.a.b.c</button>
</div>
</template>

<script lang="ts" setup name="Person">
import {reactive,watch} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18
})
let obj = reactive({
a:{
b:{
c:666
}
}
})
// 方法
function changeName(){
person.name += '~'
}
function changeAge(){
person.age += 1
}
function changePerson(){
// person = { name: '李四', age: 80 }//直接修改,不行
// person = reactive({ name: '李四', age: 80 })//reactive包裹修改,也不行
Object.assign(person,{name:'李四',age:80})//本质上替换了原person的每个属性值
}
function test(){
obj.a.b.c = 888
}

// 监视,情况三:监视【reactive】定义的【对象类型】数据,且默认是开启深度监视的
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
})
watch(obj,(newValue,oldValue)=>{
console.log('Obj变化了',newValue,oldValue)
})
</script>

情况四

监视refreactive定义的【对象类型】数据中的某个属性,注意点如下:

  1. 若该属性值不是【对象类型】即【基本类型】,需要写成函数形式,此时oldValue是旧值,newValue是新值。
  2. 若该属性值是依然是【对象类型】,可直接编,也可写成函数,建议写成函数。
  • 直接写:可以监视到对象内部属性a,b…的变化,但是监视不到整体的变化。整体改变时,对象地址值变化了,所以监视不到了。

  • 写函数(不开启深度监视):监视不到对象内部属性a,b…的变化,但是可以监视到整体的变化,函数返回值监视的是对象的地址值,改变整体是产生一个新对象,所以能监视到,并且新值是新值,旧值是旧值。(不过对象内部属性a,b…的新旧值都是新值)

  • 写函数(开启深度监视)推荐:既能监视到对象内部属性a,b…的变化,也可以监视到整体的变化,函数返回值监视的是对象的地址值,改变整体是产生一个新对象,所以能监视到,并且新值是新值,旧值是旧值。(不过对象内部属性a,b…的新旧值都是新值)

情况五

监视上述的多个数据

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
<template>
<div>
<h1>情况五:监视上述的多个数据</h1>
<h2>姓名:{{ person.name }}</h2>
<h2>年龄:{{ person.age }}</h2>
<h2>汽车:{{ person.car.c1 }}、{{ person.car.c2 }}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="changeC1">修改第一台车</button>
<button @click="changeC2">修改第二台车</button>
<button @click="changeCar">修改整个车</button>
</div>
</template>

<script lang="ts" setup name="Person">
import { reactive,watch } from "vue";
let person = reactive({
name:'张三',
age:18,
car:{
c1:'奔驰',
c2:'宝马'
}
})
function changeName() {
person.name += '~'
}
function changeAge() {
person.age += 1
}
function changeC1(){
person.car.c1 = '奥迪'
}
function changeC2(){
person.car.c2 = '大众'
}
function changeCar(){
person.car = {c1:'雅迪',c2:'爱玛'}
}
watch([() => person.name, person.car], (newValue, oldValue) =>{
console.log('person.car变化了', newValue, oldValue)
}, {deep:true})

</script>

watchEffect

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行该函数

  • watch对比watchEffect
    1. 都能监听响应式数据的变化,不同的是监听数据变化的方式不同
    2. watch:要明确指出监视的数据
    3. watchEffect:不用明确指出监视的数据(函数中用到哪些属性,就监视哪些属性)
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
<template>
<div class="person">
<h1>需求:水温达到50℃,或水位达到20cm,则联系服务器</h1>
<h2 id="demo">水温:{{temp}}</h2>
<h2>水位:{{height}}</h2>
<button @click="changePrice">水温+1</button>
<button @click="changeSum">水位+10</button>
</div>
</template>

<script lang="ts" setup name="Person">
import {ref,watch,watchEffect} from 'vue'
// 数据
let temp = ref(0)
let height = ref(0)

// 方法
function changePrice(){
temp.value += 10
}
function changeSum(){
height.value += 1
}

// 用watch实现,需要明确的指出要监视:temp、height
watch([temp,height],(value)=>{
// 从value中获取最新的temp值、height值
const [newTemp,newHeight] = value
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(newTemp >= 50 || newHeight >= 20){
console.log('联系服务器')
}
})

// 用watchEffect实现,不用
const stopWtach = watchEffect(()=>{
// 室温达到50℃,或水位达到20cm,立刻联系服务器
if(temp.value >= 50 || height.value >= 20){
console.log(document.getElementById('demo')?.innerText)
console.log('联系服务器')
}
// 水温达到100,或水位达到50,取消监视
if(temp.value === 100 || height.value === 50){
console.log('清理了')
stopWtach()
}
})
</script>

标签的ref属性

用于注册模板的引用

  • 用在普通的DOM标签上,获取的是DOM节点
  • 用在组件标签上,获取的是组件实例对象
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
<template>
<div class="person">
<h1 ref="title1">尚硅谷</h1>
<h2 ref="title2">前端</h2>
<h3 ref="title3">Vue</h3>
<input type="text" ref="input"> <br><br>
<button @click="showLog">点我打印内容</button>
</div>
</template>

<script lang="ts" setup name="Person">
import {ref} from 'vue'
let title1 = ref()
let title2 = ref()
let title3 = ref()
function showLog() {
//通过id获取元素
// const t1 = document.getElementById('title1')
// console.log((t1 as HTMLElement).innerText)
// console.log((<HTMLElement>t1).innerText)
// console.log(t1?.innerText)
//通过ref获取元素
console.log(title1.value)
console.log(title2.value)
console.log(title3.value)
}

</script>

未完待续…