进行vue3学习跟随尚硅谷vue3
一.vue3简介
Vue是一套用于构建用户界面的渐进式 JavaScript 框架。它由尤雨溪创建,以其易学性、灵活性和高性能而广受欢迎。
"渐进式"意味着你可以:
- 从一个轻量、核心的库开始,只用于增强静态 HTML。
- 逐步应用到复杂的、大规模的单页应用。
优点:
- 更快的性能:重写了虚拟 DOM,优化了编译策略。
- 更小的体积:通过更好的 Tree-shaking(树摇优化),使打包后的文件更小。
- 更强的可维护性:采用 Monorepo 管理源码,结构更清晰。
- 更好的 TypeScript 支持:使用 TypeScript 重写,提供了完美的类型推断。
- 更先进的 API :引入了 Composition API,提供了更好的逻辑复用和组织方式。
二.创建vue3工程
在前端,我们一般使用vscode进行构建
创建vue3工程时,确保自己已经安装了node.js,最新版本可以在官网下载。安装完毕后在桌面打开终端按照如图进行创建最初的vue3工程。

之后桌面就会出现一个如图所示的文件夹

之后,将该工程使用vscode打开,系统自动提示安装vue插件,一键安装即可
进入vscode后,发现部分项目标红,打开终端输入npm i下载所有依赖,出现node_modules文件夹,这里面就是我们自动安装的所有依赖。重启后标红消失
此时可以安装插件Vue (Official)


文件解释
目录与其中文件介绍:
.vscode/extensions.json是所下载插件
public是页签图标
src是我们主要的工作成果/源代码文件
在这里原先的assets和components文件夹,App.vue需要重新创建,最新版本已经去除
ts
//main.ts文件
//引入creatApp用于创建应用
import { createApp } from "vue";
//引入App根组件
import App from './App.vue'
createApp(App).mount('#app')
文件介绍:
env.d.ts自动声明所有可能用到的的文件
index.html入口文件
两个package包的管理文件
三个tsconfig文件,管理js文件
vite.config.ts安装插件,配置代理
三.vue3核心语法
1.【OptionsAPI 与 CompositionAPI】
| 特性 | Options API | Composition API |
|---|---|---|
| 编程范式 | 选项式、声明式。你告诉 Vue "你需要什么"(data, methods, computed)。 | 函数式、组合式。你像写普通 JavaScript 函数一样组织代码。 |
| 代码组织 | 按选项类型组织 。相同逻辑的代码被拆分到不同的选项中(如 data, methods, watch)。 |
按逻辑功能组织。所有属于同一功能的代码(响应式数据、方法、计算属性等)可以放在一起。 |
| 核心语法 | data, methods, computed, watch, lifecycle hooks 等选项。 |
ref, reactive, computed, watch, onMounted 等函数。 |
| 逻辑复用 | Mixins / 混入。容易发生命名冲突,来源不清晰,关系不明确。 | 自定义组合式函数。利用纯 JavaScript 函数,返回响应式数据和方法,清晰无冲突。 |
| TypeScript 支持 | 支持较弱,类型推断有时会困难。 | 原生支持极好,能利用完整的 TS 类型推断。 |
| 学习曲线 | 较低,对初学者友好,结构固定,易于理解。 | 较高,需要理解响应式原理和函数式编程思想,但灵活性更强。 |
| this 的使用 | 大量使用 this 来访问组件实例的属性和方法。 |
几乎不使用 this 。Composition API 的函数在 setup() 或 <script setup> 的范围内直接调用。 |
| 适用场景 | 中小型项目,逻辑不复杂的组件,初学者团队。 | 大型项目,需要高复用性、可读性和 TypeScript 支持的重型组件。 |
vue2中使用的是OptionAPI,而vue3使用的是CompositionAPI
这里测试一个非响应式的demo
仅以该段为例,在vue2中,要将data,methodl分开进行书写,而vue3中可以直接进行初始化,之后的方法分区块书写,最后将所有数据进行提交
vue
<template>
<div class="person">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
<button @click="showTel">查看联系方式</button>
</div>
</template>
<script lang="ts">
export default {
name:'Person',
beforeCreate(){//生命周期
console.log('beforeCreate')
},
setup(){
console.log(this) //setup中的this是undefined,Vue3在弱化this了
// 数据,原来是写在data中的,此时的name、age、tel都不是响应式的数据
let name = '张三'
let age = 18
let tel = '13888888888'
// 方法
function changeName() {
name = 'zhang-san' //注意:这样修改name,页面是没有变化的
console.log(name) //name确实改了,但name不是响应式的
}
function changeAge() {
age += 1 //注意:这样修改age,页面是没有变化的
console.log(age) //age确实改了,但age不是响应式的
}
function showTel() {
alert(tel)
}
// 将数据、方法交出去,模板中才可以使用
return {name,age,tel,changeName,changeAge,showTel}
}
}
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
</style>
2.setup函数
setup 函数,它是 Composition API 的入口和核心。
setup 是一个专门用于使用 Composition API 的组件选项。它在组件实例被创建之前 执行,在 beforeCreate 和 created 生命周期钩子之前执行。它的主要目的是:
- 定义响应式数据
- 定义方法
- 注册生命周期钩子
- 返回所有需要在模板中使用的数据和方法
set up中不能用this
3.data,methods,setup能否同时存在于同一组件
data、methods 和 setup 可以同时存在于同一个组件中。Vue 3 设计上完全支持 Options API 和 Composition API 的混合使用。
当三者同时存在时,Vue 会按照以下规则处理:
- setup 函数最先执行
- data 和 methods 在组件实例创建时合并
- 命名冲突时,setup 返回的属性优先级最高,然后是data,methods
vue
<template>
<div>
<h2>混合使用示例</h2>
<p>来自 data: {{ message }}</p>
<p>来自 setup: {{ count }}</p>
<p>计算属性: {{ combinedMessage }}</p>
<button @click="incrementFromSetup">Setup 方法 (+1)</button>
<button @click="incrementFromMethods">Methods 方法 (+10)</button>
<button @click="showInfo">显示所有数据</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
// 1. data 选项
data() {
return {
message: 'Hello from data',
countFromData: 100
}
},
// 2. methods 选项
methods: {
incrementFromMethods() {
this.countFromData += 10
console.log('Methods count:', this.countFromData)
console.log('Setup count:', this.count) // 可以访问 setup 返回的数据
},
showInfo() {
console.log('=== 所有数据 ===')
console.log('data.message:', this.message)
console.log('data.countFromData:', this.countFromData)
console.log('setup.count:', this.count)
console.log('setup.doubleCount:', this.doubleCount)
}
},
// 3. setup 函数
setup() {
// setup 中的响应式数据
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
// setup 中的方法
const incrementFromSetup = () => {
count.value++
console.log('Setup count:', count.value)
}
// 返回给模板使用的数据和方法
return {
count,
doubleCount,
incrementFromSetup
}
},
// 其他 Options API 也可以正常使用
computed: {
combinedMessage() {
return `${this.message} - ${this.count}`
}
},
mounted() {
console.log('组件已挂载')
console.log('可以在 mounted 中访问所有数据:', this.count, this.message)
}
}
</script>
4.set up语法糖
<script setup> 是 Vue 3 中 Composition API 的语法糖,它让代码更加简洁、易读,是目前最推荐的写法。在这其中写的东西就相当于写的set up函数
这是普通setup函数
vue
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => {
count.value++
}
return {
count,
double,
increment
}
}
}
</script>
上述代码,还需要编写一个不写setup的script标签,去指定组件名字,比较麻烦,我们可以借助vite中的插件简化
- 第一步:
npm i vite-plugin-vue-setup-extend -D - 第二步:
vite.config.ts在插件包中导入 - 第三步:
<script setup lang="ts" name="Person">
vue
//使用setup语法糖后
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => {
count.value++
}
</script>
5.响应式数据
5.1ref定义基本类型
ref,这是 Composition API 中最核心的响应式 API 之一,也是 Vue 3 中用于创建响应式引用的函数。它可以包装任何基本类型的值,使其变成响应式的
可以定义基本类型,对象类型的响应式数据
想让谁是响应式,给谁外面包一个ref
语法: let xxx = ref(初始值)。
**返回值:**一个RefImpl的实例对象,简称ref对象或ref,ref对象的value属性是响应式的。
另外,JS中操作数据需要:xxx.value,但模板中不需要.value,直接使用即可。
vue
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)//这里count不是响应式,count.value才是响应式
const increment = () => {
// 注意:在 JavaScript 中需要通过 .value 访问
count.value++
console.log(count.value) // 1
console.log(count) // RefImpl 对象
}
</script>
5.2reactive响应式对象
reactive 是 Vue 3 中用于创建响应式对象的函数,是 Composition API 中另一个核心的响应式 API。它接收一个普通 JavaScript 对象,并返回该对象的响应式代理。
只能定义对象类型的响应式数据
reactive只要包裹了一个对象,就变成了Proxy()响应式对象
语法: let 响应式对象= reactive(源对象)。
**返回值:**一个Proxy的实例对象,简称:响应式对象。
注意点: reactive定义的响应式数据是"深层次"的。
js
import { reactive } from 'vue'
// 创建响应式对象
const state = reactive({
count: 0,
message: 'Hello',
user: {
name: 'Alice',
age: 25
},
items: ['apple', 'banana']
})
5.3ref 定义对象类型的响应式数据
若ref接收的是对象类型,内部其实也是调用了reactive函数。
vue
<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>
5.4ref和reactive对比
| 特性 | ref |
reactive |
|---|---|---|
| 基本定义 | 创建响应式引用,可包装任何值 | 创建响应式对象,仅用于对象类型 |
| 数据类型 | ✅ 任何类型:基本类型、对象、数组等 | ❌ 仅对象类型(Object、Array、Map、Set) |
| 创建方式 | const count = ref(0) |
const state = reactive({ count: 0 }) |
| 访问方式 | 需要通过 .value 访问 |
直接访问属性 |
| 模板使用 | 自动解包,无需 .value |
直接使用属性 |
| 重新赋值 | ✅ 支持:ref.value = newValue |
❌ 不支持直接替换整个对象 |
| 解构响应式 | 本身就是引用,解构后仍需要通过 .value |
需要使用 toRefs 保持响应式 |
| TypeScript 支持 | ✅ 优秀,完整的类型推断 | ⚠️ 一般,复杂嵌套类型推断可能有问题 |
| 深度响应式 | ✅ 默认深度响应式 | ✅ 默认深度响应式 |
| 性能 | 轻微开销(包装对象) | 直接 Proxy,性能稍好 |
| 适用场景 | 基本类型、需要重新赋值的引用类型、模板 ref | 复杂的嵌套对象、表单数据、应用状态 |
5.5toRefs 与 toRef
都是把一个响应式对象里的属性拿出来变成响应式
| 特性 | toRef | toRefs |
|---|---|---|
| 作用对象 | 单个响应式对象的单个属性 | 整个响应式对象的所有属性 |
| 返回值 | 单个 ref 对象 | 包含所有属性 ref 的对象 |
| 使用场景 | 提取单个属性 | 解构整个响应式对象 |
js
<template>
<div class="person">
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}},{{nl}}</h2>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年龄</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {reactive,toRefs,toRef} from 'vue'
// 数据
let person = reactive({
name:'张三',
age:18
})
// 使用toRefs从person这个响应式对象中,解构出name、age,且name和age依然是响应式的
// name和age的值是ref类型,其value值指向的是person.name和person.age
let {name,age} = toRefs(person)//toRefs(person)包含name和age
let nl = toRef(person,'age')//nl只包含person中的age
console.log(nl.value)
// 方法
function changeName(){
name.value += '~'
console.log(name.value,person.name)
}
function changeAge(){
age.value += 1
}
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
5.6计算属性comnputed
计算属性是基于响应式依赖进行缓存的派生值,只有当依赖发生变化时才会重新计算。
官网建议:计算属性的返回值应该被视为只读的,并且永远不应该被更改,应该更新它所依赖的源状态以触发新的计算
js
<template>
<div class="person">
姓:<input type="text" v-model="firstName"> <br>
名:<input type="text" v-model="lastName"> <br>
全名:<span>{{fullName}}</span> <br>
<button @click="changeFullName">全名改为:li-si</button>
</div>
</template>
<script setup lang="ts" name="App">
import {ref,computed} from 'vue'
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){
console.log('有人修改了fullName',val)
firstName.value = val.split('-')[0]
lastName.value = val.split('-')[1]
}
})
function changeFullName(){
fullName.value = 'li-si'
}
</script>
5.7watch监视
watch 用于监听响应式数据的变化,并在变化时执行副作用操作。
vue3只能监视以下四种数据:
ref定义的数据。reactive定义的数据。- 函数返回一个值(
getter函数)。 - 一个包含上述内容的数组。
情况一监视ref基本类型数据
vue
<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】定义的【基本类型】数据,注意这里sum不用加value
const stopWatch = watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
if(newValue >= 10){
stopWatch()
}
})
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
情况二监视ref对象类型数据
vue
<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})
//这里watch第一个对象是person,第二个对象是36行
//newValue,oldValue有时是一样的,有时不一样
//这里理解为,你有一个旧房子,现在又买了一个新房子,两个事物是不一样的,但是旧房子地址没有变,所以仍然存在
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
情况三监视reactive对象
监视reactive定义的【对象类型】数据,且默认开启了深度监视
隐式创建深层监听,不能关闭
vue
<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(){
Object.assign(person,{name:'李四',age:80})//reactive对象不能整体修改,需要使用assign进行逐个修改
//简单来说,ref就是重新买个一个房子,建了一个新的对象。而reactive就是把房子装修了,修改了其中值
}
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>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
情况四监视ref或reactive定义的【对象类型】数据中的某个属性
有以下两点注意:
- 若该属性值不是【对象类型】,需要写成函数形式。
- 若该属性值是依然是【对象类型】,可直接编,也可写成函数,建议写成函数。
监视的要是对象里的属性,那么最好写函数式,注意点:若是对象监视的是地址值,需要关注对象内部,需要手动开启深度监视。
vue
<template>
<div class="person">
<h1>情况四:监视【ref】或【reactive】定义的【对象类型】数据中的某个属性</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,(newValue,oldValue)=>{
console.log('person.name变化了',newValue,oldValue)
}) */
// 监视,情况四:监视响应式对象中的某个属性,且该属性是对象类型的,可以直接写,也能写函数,更推荐写函数
watch(()=>person.car,(newValue,oldValue)=>{
console.log('person.car变化了',newValue,oldValue)
},{deep:true})
//这里写成函数式更佳,监视地址
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
情况五监视多个数据
vue
<template>
<div class="person">
<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>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
5.8watchEffect
watchEffect 是一个自动依赖追踪的监听器,它会立即执行传入的函数,并自动追踪函数内部使用的所有响应式依赖。
watch监视谁输出谁;watchEffect自动分析监视谁,启动先运行
| 特性 | watch |
watchEffect |
|---|---|---|
| 依赖声明 | 显式声明监听源 | 自动追踪依赖 |
| 立即执行 | 需要 immediate: true |
总是立即执行 |
| 参数获取 | 提供新旧值 | 不提供参数 |
| 使用复杂度 | 相对复杂 | 简单直接 |
| 适用场景 | 精确控制监听源 | 自动依赖追踪 |
vue
<template>
<div class="person">
<h2>需求:当水温达到60度,或水位达到80cm时,给服务器发请求</h2>
<h2>当前水温:{{temp}}℃</h2>
<h2>当前水位:{{height}}cm</h2>
<button @click="changeTemp">水温+10</button>
<button @click="changeHeight">水位+10</button>
</div>
</template>
<script lang="ts" setup name="Person">
import {ref,watch,watchEffect} from 'vue'
// 数据
let temp = ref(10)
let height = ref(0)
// 方法
function changeTemp(){
temp.value += 10
}
function changeHeight(){
height.value += 10
}
// 监视 -- watch实现
/* watch([temp,height],(value)=>{
// 从value中获取最新的水温(newTemp)、最新的水位(newHeight)
let [newTemp,newHeight] = value
// 逻辑
if(newTemp >= 60 || newHeight >= 80){
console.log('给服务器发请求')
}
}) */
// 监视 -- watchEffect实现
watchEffect(()=>{
if(temp.value >= 60 || height.value >= 80){
console.log('给服务器发请求')
}
})
</script>
<style scoped>
.person {
background-color: skyblue;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
}
button {
margin: 0 5px;
}
li {
font-size: 20px;
}
</style>
5.9标签的 ref 属性
作用:用于注册模板引用。
常用
- 防止样式污染:组件样式不会影响其他组件
- 避免命名冲突:相同类名在不同组件中互不干扰
- 组件样式独立:每个组件拥有独立的 CSS 作用域
- 用在普通
DOM标签上,获取的是DOM节点。- 用在组件标签上,获取的是组件实例对象。
| 场景 | ref 绑定对象 | 返回值类型 |
|---|---|---|
| DOM 元素 | <div ref="divRef"> |
HTMLElement |
| 输入框 | <input ref="inputRef"> |
HTMLInputElement |
| 按钮 | <button ref="buttonRef"> |
HTMLButtonElement |
| Vue 组件 | <Child ref="childRef"> |
组件实例 |
| v-for 中的元素 | <li v-for ref="listRef"> |
元素数组 |
5.10props的使用
Props 是组件之间数据传递的一种方式,父组件通过 props 向子组件传递数据。
js
// 定义一个接口,限制每个Person对象的格式
export interface PersonInter {
id:string,
name:string,
age:number
}
// 定义一个自定义类型Persons
export type Persons = Array<PersonInter>
父亲App.vue中代码:
vue
<template>
<Person :list="persons"/>
</template>
<script lang="ts" setup name="App">
import Person from './components/Person.vue'
import {reactive} from 'vue'
import {type Persons} from './types'
let persons = reactive<Persons>([
{id:'e98219e12',name:'张三',age:18},
{id:'e98219e13',name:'李四',age:19},
{id:'e98219e14',name:'王五',age:20}
])
</script>
儿子Person.vue中代码:
vue
<template>
<div class="person">
<ul>
<li v-for="item in list" :key="item.id">
{{item.name}}--{{item.age}}
</li>
</ul>
</div>
</template>
<script lang="ts" setup name="Person">
import {defineProps} from 'vue'
import {type PersonInter} from '@/types'
// 第一种写法:仅接收
// const props = defineProps(['list'])
// 第二种写法:接收+限制类型
// defineProps<{list:Persons}>()
// 第三种写法:接收+限制类型+指定默认值+限制必要性
let props = withDefaults(defineProps<{list?:Persons}>(),{
list:()=>[{id:'asdasg01',name:'小猪佩奇',age:18}]
})
console.log(props)
</script>
6.生命周期(组件的一生)
在 Vue 3 中,生命周期钩子函数是组件在不同阶段执行的函数,让你可以在特定时机执行自定义逻辑。Vue 3 的生命周期与 Vue 2 类似,但有一些变化和组合式 API 的引入。
Vue3中,生命周期分八个阶段,即对应8个与created类似的钩子函数。
- setup(组合式 API)
- 触发时机:在组件实例被创建之初、任何选项式 API 之前执行
- 主要作用:组合式 API 的入口,定义响应式数据、计算属性、方法等
- 注意事项:没有对应的选项式 API 钩子
- beforeCreate
- 触发时机:组件实例初始化之后,数据观测和事件/侦听器配置之前
- 主要作用:执行与响应式数据无关的初始化逻辑
- 数据状态:数据和方法还未初始化
- created
- 触发时机:组件实例创建完成之后
- 主要作用:发起异步请求、执行不依赖 DOM 的初始化操作
- 数据状态:可以访问数据和方法,但尚未挂载到 DOM
- beforeMount
- 触发时机:挂载开始之前,模板编译完成后
- 主要作用:在组件首次渲染到 DOM 之前执行
- DOM 状态:this.$el 还不存在
- mounted
- 触发时机:组件挂载到 DOM 之后
- 主要作用:操作 DOM、初始化第三方库
- DOM 状态:可以访问到渲染后的 DOM
- beforeUnmount(Vue 3)/ beforeDestroy(Vue 2)
- 触发时机:组件卸载之前
- 主要作用:清理定时器、取消网络请求、移除事件监听
- 组件状态:组件实例仍然完全可用
- updated
- 触发时机:数据更改导致虚拟 DOM 重新渲染和打补丁之后
- 主要作用:执行依赖于 DOM 更新的操作
- 注意事项:避免在此钩子中修改状态,防止无限循环
- unmounted(Vue 3)/ destroyed(Vue 2)
- 触发时机:组件卸载并销毁之后
- 主要作用:执行最后的清理工作
- 组件状态:所有指令和事件监听器已被移除,子组件已被销毁

vue
<template>
<div class="person">
<h2>当前求和为:{{ sum }}</h2>
<button @click="changeSum">点我sum+1</button>
</div>
</template>
<!-- vue3写法 -->
<script lang="ts" setup name="Person">
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 数据
let sum = ref(0)
// 方法
function changeSum() {
sum.value += 1
}
console.log('setup')
// 生命周期钩子
onBeforeMount(()=>{
console.log('挂载之前')
})
onMounted(()=>{
console.log('挂载完毕')
})
onBeforeUpdate(()=>{
console.log('更新之前')
})
onUpdated(()=>{
console.log('更新完毕')
})
onBeforeUnmount(()=>{
console.log('卸载之前')
})
onUnmounted(()=>{
console.log('卸载完毕')
})
</script>
7.自定义hook
自定义 Hook 是一个可重用的函数,用于封装和复用 Vue 组件的逻辑。它基于 Vue 3 的组合式 API,让开发者能够将相关的逻辑代码组织在一起。
示例代码:
-
useSum.ts中内容如下:jsimport {ref,onMounted} from 'vue' export default function(){ let sum = ref(0) const increment = ()=>{ sum.value += 1 } const decrement = ()=>{ sum.value -= 1 } onMounted(()=>{ increment() }) //向外部暴露数据 return {sum,increment,decrement} } -
useDog.ts中内容如下:jsimport {reactive,onMounted} from 'vue' import axios,{AxiosError} from 'axios' export default function(){ let dogList = reactive<string[]>([]) // 方法 async function getDog(){ try { // 发请求 let {data} = await axios.get('https://dog.ceo/api/breed/pembroke/images/random') // 维护数据 dogList.push(data.message) } catch (error) { // 处理错误 const err = <AxiosError>error console.log(err.message) } } // 挂载钩子 onMounted(()=>{ getDog() }) //向外部暴露数据 return {dogList,getDog} } -
组件中具体使用:
vue<template> <h2>当前求和为:{{sum}}</h2> <button @click="increment">点我+1</button> <button @click="decrement">点我-1</button> <hr> <img v-for="(u,index) in dogList.urlList" :key="index" :src="(u as string)"> <span v-show="dogList.isLoading">加载中......</span><br> <button @click="getDog">再来一只狗</button> </template> <script lang="ts"> import {defineComponent} from 'vue' export default defineComponent({ name:'App', }) </script> <script setup lang="ts"> import useSum from './hooks/useSum' import useDog from './hooks/useDog' let {sum,increment,decrement} = useSum() let {dogList,getDog} = useDog() </script>