Vue3 核心语法进阶:从基础到实战的全面指南
Vue3 作为目前前端主流框架之一,其 Composition API 带来了更灵活的代码组织方式和更高效的性能。本文将基于 Vue3 核心语法进阶知识点,从计算属性、监听器到自定义 Hook,逐一拆解关键技术,结合代码示例让你快速掌握 Vue3 进阶开发能力
一、computed 计算属性:高效处理衍生数据
computed 的核心作用是根据已有响应式数据计算新数据,且自带缓存机制——只有依赖数据发生变化时,才会重新计算,避免重复执行冗余逻辑
1.1 只读计算属性(最常用)
适用于仅需要 “根据依赖生成新值”,无需修改计算结果的场景(如拼接姓名、计算总价等)
示例:
<template>
<div class="person">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
全名:<span>{{ fullName }}</span>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
// 基础响应式数据
const firstName = ref('zhang')
const lastName = ref('san')
// 只读计算属性:依赖firstName和lastName生成全名
const fullName = computed(() => {
return firstName.value + '-' + lastName.value
})
</script>
关键说明:
① 计算属性通过computed(() => { ... })定义,返回值即为计算结果
② 访问时直接用fullName(模板中)或fullName.value(脚本中),无需加括号
1.2 可读写计算属性
适用于需要 “修改计算结果,并反向更新依赖数据” 的场景(如通过全名反向修改姓和名)
示例:
<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">
import { ref, computed } from 'vue'
const firstName = ref('zhang')
const lastName = ref('san')
// 可读写计算属性:包含get(读取)和set(修改)方法
const fullName = computed({
// 读取:生成计算结果
get() {
return firstName.value + '-' + lastName.value
},
// 修改:接收新值,反向更新依赖数据
set(val) {
const [newFirst, newLast] = val.split('-')
firstName.value = newFirst
lastName.value = newLast
}
})
// 触发修改计算属性
const changeFullName = () => {
fullName.value = 'li-si' // 调用set方法
}
</script>
二、watch 监听器:精准监听数据变化
watch 的核心作用是监视响应式数据的变化,并在变化时执行自定义逻辑(如数据持久化、发送请求等)。Vue3 中 watch 仅支持四种数据类型:ref 定义的数据、reactive 定义的数据、getter 函数返回值、包含上述内容的数组,具体分为五种使用场景
2.1 场景一:监视 ref 定义的基本类型数据
适用于监听ref(Number/String/Boolean)等基本类型数据,直接传入数据名即可,无需额外配置
示例:
<template>
<div>
当前求和:{{ sum }}
<button @click="sum++">sum+1</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const sum = ref(0)
// 监视ref基本类型:直接传sum,监听其value变化
const stopWatch = watch(sum, (newVal, oldVal) => {
console.log('sum变化:', newVal, oldVal)
// 可选:满足条件时停止监听
if (newVal >= 10) stopWatch()
})
</script>
2.2 场景二:监视 ref 定义的对象类型数据
适用于监听ref({ ... })对象类型数据,默认仅监听对象的地址值变化;若需监听对象内部属性变化,需手动开启 deep:true
注意点:
① 修改对象内部属性时,newVal和oldVal指向同一个对象(地址未变)
② 替换整个对象时,newVal是新对象,oldVal是旧对象(地址已变)
示例:
<template>
<div>
姓名:{{ person.name }} | 年龄:{{ person.age }}<br>
<button @click="person.name += '~'">修改名字</button>
<button @click="person = { name: '李四', age: 20 }">替换整个人</button>
</div>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
const person = ref({ name: '张三', age: 18 })
// 监视ref对象:开启deep监听内部属性
watch(person, (newVal, oldVal) => {
console.log('person变化:', newVal, oldVal)
}, { deep: true }) // 必须加deep,否则仅监听地址变化
</script>
2.3 场景三:监视 reactive 定义的对象类型数据
适用于监听reactive({ ... })对象类型数据,默认开启深度监听,无需手动配置 deep:true,即使嵌套层级较深也能监听
示例:
<template>
<div>
嵌套数据:{{ obj.a.b.c }}
<button @click="obj.a.b.c = 888">修改c的值</button>
</div>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue'
// reactive定义的嵌套对象
const obj = reactive({ a: { b: { c: 666 } } })
// 监视reactive对象:默认深度监听
watch(obj, (newVal) => {
console.log('obj变化:', newVal.a.b.c) // 能监听到c的变化
})
</script>
2.4 场景四:监视对象中的某个属性
适用于仅监听对象中的单个属性(如person.name),需通过getter函数返回该属性(推荐写法,避免歧义)。若属性是对象类型,仍需手动开启 deep:true
示例:
<template>
<div>
姓名:{{ person.name }} | 第一辆车:{{ person.car.c1 }}<br>
<button @click="person.name += '~'">修改名字</button>
<button @click="person.car.c1 = '奥迪'">修改汽车</button>
</div>
</template>
<script setup lang="ts">
import { reactive, watch } from 'vue'
const person = reactive({
name: '张三',
car: { c1: '奔驰', c2: '宝马' }
})
// 1. 监听基本类型属性(person.name):getter函数返回属性
watch(() => person.name, (newVal) => {
console.log('姓名变化:', newVal)
})
// 2. 监听对象类型属性(person.car):getter函数+deep
watch(() => person.car, (newVal) => {
console.log('汽车变化:', newVal.c1)
}, { deep: true })
</script>
2.5 场景五:监视多个数据
适用于同时监听多个响应式数据,将多个数据(或 getter 函数)放入数组即可,回调函数中newVal和oldVal也对应为数组
示例:
<script setup lang="ts">
import { reactive, watch } from 'vue'
const person = reactive({ name: '张三', age: 18 })
// 监视多个数据:数组形式传入
watch([() => person.name, () => person.age], (newVal, oldVal) => {
console.log('姓名/年龄变化:', newVal, oldVal)
// newVal[0]是姓名新值,newVal[1]是年龄新值
})
</script>
三、watchEffect:自动追踪依赖的监视器
watchEffect 是vue3新增的监听器,核心特点是无需明确指定监视的数据,会自动踪函数内部使用的响应式数据,数据变化时重新执行函数,且函数会立即执行一次(初始化触发)
3.1 与 watch 和核心区别
| 特性 | watch | watchEffect |
|---|---|---|
| 监视目标 | 需明确指定(如 sum、()=>person.name) | 自动追踪函数内的响应式数据 |
| 初始化执行 | 不执行(除非配置 immediate: true) | 立即执行一次 |
| 适用场景 | 需知道旧值、需控制执行时机 | 无需旧值、初始化即执行的场景 |
3.2 使用示例:水温 / 水位监控
需求:水温≥50℃或水位≥20cm 时联系服务器,水温≥100℃或水位≥50cm 时停止监听
代码示例:
<template>
<div>
水温:{{ temp }}℃ | 水位:{{ height }}cm<br>
<button @click="temp += 10">水温+10</button>
<button @click="height += 5">水位+5</button>
</div>
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue'
const temp = ref(0)
const height = ref(0)
// watchEffect:自动追踪temp和height
const stopWatch = watchEffect(() => {
// 初始化时会执行一次,后续temp/height变化时重新执行
if (temp.value >= 50 || height.value >= 20) {
console.log('联系服务器')
}
// 满足条件停止监听
if (temp.value >= 100 || height.value >= 50) {
stopWatch()
console.log('停止监听')
}
})
</script>
四、ref 属性:获取DOM与组件实例
ref除了定义响应式数据,还可用于注册模板引用,即获取 DOM 节点或子组件实例,方便操作 DOM 或调用子组件方法
4.1 用在普通DOM标签上
通过ref="变量名"给 DOM 标签命名,在脚本中用ref()定义同名变量,即可通过变量名.value获取 DOM 节点
示例:
<template>
<div>
<h1 ref="title">Vue3核心语法</h1>
<input ref="inputRef" type="text" placeholder="请输入">
<button @click="logDom">打印DOM信息</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
// 定义与模板ref同名的变量
const title = ref<HTMLElement | null>(null)
const inputRef = ref<HTMLInputElement | null>(null)
const logDom = () => {
console.log('标题文本:', title.value?.innerText) // 输出"Vue3核心语法"
console.log('输入框值:', inputRef.value?.value) // 输出输入框内容
}
</script>
4.2 用在组件标签上
通过ref="变量名"给子组件命名,可获取子组件实例,但子组件需通过defineExpose主动暴露需要外部访问的属性 / 方法(否则无法访问)
父组件代码:
<template>
<div>
<Person ref="personRef" />
<button @click="getComponentData">获取子组件数据</button>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Person from './Person.vue'
// 定义与组件ref同名的变量
const personRef = ref<any>(null)
const getComponentData = () => {
// 访问子组件暴露的name和age
console.log('子组件姓名:', personRef.value.name)
console.log('子组件年龄:', personRef.value.age)
}
</script>
子组件代码(Person.vue):
<script setup lang="ts">
import { ref, defineExpose } from 'vue'
const name = ref('张三')
const age = ref(18)
// 主动暴露属性(必须用defineExpose,否则父组件无法访问)
defineExpose({ name, age })
</script>
五、props:组件间数据传递与类型约束
props 是父子组件间传递数据的核心方式,Vue3 中通过defineProps定义 props,并支持 TypeScript 类型约束、默认值设置、必要性校验
5.1 三种常用写法
写法1:仅接收 props (无约束)
适用于简单场景,仅声明接收的 props 名称,无类型或默认值约束
const props = defineProps(['list', 'title'])
写法2:接收 + TypeScript 类型约束
适用于需要明确 props 类型的场景,通过泛型指定类型
// 1. 先定义类型(可单独放在types.ts文件中)
interface Person {
id: string
name: string
age: number
}
type PersonList = Person[]
// 2. 定义props并指定类型
defineProps<{
list: PersonList // 必传,且类型为PersonList
title?: string // 可选,类型为string
}>()
写法3:接收 + 类型 + 默认值 + 必要性
适用于需要设置默认值的场景,通过withDefaults包装defineProps
import { withDefaults, defineProps } from 'vue'
// 定义类型
interface Person { id: string; name: string; age: number }
type PersonList = Person[]
// 设置默认值(list可选,默认值为[{id: '01', name: '默认', age: 18}])
const props = withDefaults(defineProps<{
list?: PersonList
title?: string
}>(), {
// 复杂类型(如数组、对象)需用函数返回,避免复用同一引用
list: () => [{ id: '01', name: '默认用户', age: 18 }],
title: '用户列表' // 简单类型直接赋值
})
// 使用props(无需.value,直接访问)
console.log(props.list)
console.log(props.title)
六、Vue3 生命周期钩子:组件生命周期管理
生命周期钩子是组件实例从创建到销毁过程中触发的函数,Vue3 基于 Composition API 重构了生命周期,移除了 Vue2 中的beforeCreate和created(由setup替代),其他钩子需通过导入使用
6.1 Vue2 与 Vue3 生命周期对比
| Vue2 生命周期 | Vue3 生命周期 | 触发时机 |
|---|---|---|
| beforeCreate | setup(替代) | 组件实例创建前 |
| created | setup(替代) | 组件实例创建后 |
| beforeMount | onBeforeMount | 组件挂载到 DOM 前 |
| mounted | onMounted | 组件挂载到 DOM 后(常用) |
| beforeUpdate | onBeforeUpdate | 组件数据更新前 |
| updated | onUpdated | 组件数据更新后 |
| beforeDestroy | onBeforeUnmount | 组件卸载前(常用) |
| destroyed | onUnmounted | 组件卸载后 |
6.2 常用钩子使用示例
<template>
<div>当前求和:{{ sum }}</div>
</template>
<script setup lang="ts">
import { ref, onBeforeMount, onMounted, onBeforeUnmount } from 'vue'
const sum = ref(0)
// setup:替代beforeCreate和created,组件初始化时执行
console.log('setup:组件初始化')
// 挂载前:DOM未渲染
onBeforeMount(() => {
console.log('onBeforeMount:DOM未挂载')
})
// 挂载后:DOM已渲染(常用,如初始化图表、发送请求)
onMounted(() => {
console.log('onMounted:DOM已挂载')
})
// 卸载前:组件即将销毁(常用,如清理定时器、取消请求)
onBeforeUnmount(() => {
console.log('onBeforeUnmount:组件即将卸载')
})
</script>
七、自定义Hook:复用 Composition API 逻辑
自定义 Hook 本质是封装了 Composition API 的函数,核心作用是复用组件逻辑(如数据请求、定时器、本地存储等),让setup函数更简洁,代码可维护性更高(类似 Vue2 的 mixin,但无命名冲突问题)
7.1 示例 1:创建计数器 Hook(useSum)、
需求:封装 “求和、加 1、减 1” 逻辑,供多个组件复用
Hook代码(hooks / useSum.ts):
import { ref, onMounted } from 'vue'
// 自定义Hook:函数名以use开头(规范)
export default function useSum(initialVal = 0) {
const sum = ref(initialVal)
// 加1方法
const increment = () => {
sum.value += 1
}
// 减1方法
const decrement = () => {
sum.value -= 1
}
// 生命周期:挂载后自动加1
onMounted(() => {
increment()
})
// 暴露属性和方法给外部
return { sum, increment, decrement }
}
组件中使用 Hook:
<template>
<div>
当前求和:{{ sum }}<br>
<button @click="increment">+1</button>
<button @click="decrement">-1</button>
</div>
</template>
<script setup lang="ts">
// 导入自定义Hook
import useSum from './hooks/useSum'
// 使用Hook:解构获取属性和方法
const { sum, increment, decrement } = useSum(0) // 初始值为0
</script>
7.2 示例2:创建狗狗图片请求Hook(useDog)
需求:封装 “请求狗狗图片、加载更多” 逻辑,复用数据请求逻辑
Hook代码(hooks / useDog.ts):
import { reactive, onMounted } from 'vue'
import axios from 'axios'
export default function useDog() {
// 存储狗狗图片URL列表
const dogList = reactive<string[]>([])
// 请求狗狗图片的方法
const getDog = async () => {
try {
const { data } = await axios.get('https://dog.ceo/api/breed/pembroke/images/random')
dogList.push(data.message) // 添加图片URL到列表
} catch (err) {
console.error('请求失败:', err)
}
}
// 挂载后自动请求第一张图片
onMounted(() => {
getDog()
})
return { dogList, getDog }
}
组件中使用 Hook:
<template>
<div>
<img v-for="(img, idx) in dogList" :key="idx" :src="img" width="200"><br>
<button @click="getDog">再来一只狗</button>
</div>
</template>
<script setup lang="ts">
import useDog from './hooks/useDog'
const { dogList, getDog } = useDog()
</script>
实践建议:
1、优先用computed处理衍生数据(利用缓存),而非 methods(每次渲染执行)
2、监听数据时,简单场景用watchEffect(自动追踪),复杂场景用watch(需旧值、控制时机)
3、自定义 Hook 命名以use开头(如useTimer、useFetch),遵循社区规范
更多推荐


所有评论(0)