学习参考视频:尚硅谷Vue3入门到实战,最新版vue3+TypeScript前端开发教程_哔哩哔哩_bilibili
vue3学习目标:
VUE 3 | 1、Vue3架构与设计理念 |
2、组合式API(Composition API) | |
3、常用API:ref、reactive、watch、computed | |
4、Vue3的生命周期 | |
5、组件间通信(props、emit、defineModel) | |
6、了解插槽 |
这里根据一个例子,开展本章节的知识点
computed
的例子(手动计算)
姓名:{{ firstName }} {{ lastName }}
全名:{{ fullName }}
问题:
fullName
,容易遗漏更新。computed
的例子(自动计算)
姓名:{{ firstName }} {{ lastName }}
全名:{{ fullName }}
优势:
firstName
和 lastName
),变化时自动重新计算。特性 | 无 computed |
有 computed |
---|---|---|
计算逻辑 | 需手动维护(如 fullName = firstName + lastName ) |
自动计算(基于函数返回值) |
响应式更新 | 需手动触发更新 | 依赖变化时自动更新 |
代码组织 | 逻辑分散,易出错 | 逻辑集中,易维护 |
性能优化 | 无缓存,每次访问重新计算 | 有缓存,依赖未变化时直接返回缓存值 |
适用场景 | 简单且无需频繁更新的场景 | 依赖其他数据的复杂计算或频繁访问 |
computed
核心要点缓存机制
只读性
get
和 set
: // 计算属性——既读取又修改
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]
}
})
模板中透明使用
{{ fullName }}
)。computed
?✅ 适合:
❌ 不适合:
watch
。watch
核心作用监视数据变化,执行副作用(如异步请求、DOM 操作、状态更新等)。
watch
的监视目标只能监视以下 4 种数据:
ref
定义的响应式数据 const count = ref(0)
watch(count, (newVal, oldVal) => { /* ... */ })
reactive
定义的响应式对象 const user = reactive({ name: '张三', age: 18 })
watch(() => user.name, (newVal, oldVal) => { /* ... */ }) // 监听单个属性
getter
函数(返回一个值) watch(
() => user.age + 1, // 计算属性
(newVal, oldVal) => { /* ... */ }
)
watch([count, () => user.age], ([newCount, newAge], [oldCount, oldAge]) => { /* ... */ })
watch
的 5 种常见使用场景ref
数据const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})
这里有一个小问题
监视ref
定义的【对象类型】数据:直接写数据名,监视的是对象的【地址值】,若想监视对象内部的数据,要手动开启深度监视。
注意:
若修改的是
ref
定义的对象中的属性,newValue
和oldValue
都是新值,因为它们是同一个对象。若修改整个
ref
定义的对象,newValue
是新值,oldValue
是旧值,因为不是同一个对象了。
当使用 watch
监视 ref 或 reactive 定义的对象类型数据中的某个属性时,需要注意以下规则:
若属性值不是对象类型:
const state = reactive({ count: 0, name: 'John' })
// 监视非对象属性 count
watch(() => state.count, (newVal, oldVal) => {
console.log('count changed:', newVal, oldVal)
})
若属性值仍然是对象类型:
const state = reactive({
user: { name: 'John', age: 30 },
settings: { theme: 'dark' }
})
// 监视对象属性 user - 两种写法都有效
watch(state.user, (newVal, oldVal) => { /* ... */ }) // 直接写法
watch(() => state.user, (newVal, oldVal) => { /* ... */ }) // 推荐函数写法
对象监视的是地址值:
需要手动开启深度监视:
{ deep: true }
watch(() => state.user, (newVal, oldVal) => {
console.log('user changed:', newVal)
}, { deep: true }) // 深度监视
reactive
对象的所有属性(深度监听)const user = reactive({ name: '张三', age: 18 })
watch(
() => user, // 直接监听整个对象
(newVal, oldVal) => {
console.log('user 变化了', newVal)
},
{ deep: true } // 必须开启 deep
)
情况 | 写法 | 是否需要深度监视 |
---|---|---|
监视对象中的非对象属性 | 必须函数形式 () => obj.prop |
不需要 |
监视对象中的对象属性 | 推荐函数形式 () => obj.prop |
需要监视内部变化时开启 |
监视整个对象 | 可直接写或函数形式 | 需要监视内部变化时开启 |
核心原则:当需要监视对象内部属性变化时,无论哪种情况,都应开启深度监视 { deep: true }
。
const count = ref(0)
const user = reactive({ name: '张三' })
watch(
[count, () => user.name], // 同时监听 count 和 user.name
([newCount, newName], [oldCount, oldName]) => {
console.log(`count=${newCount}, name=${newName}`)
}
)
immediate
+ debounce
)const searchQuery = ref('')
watch(
searchQuery,
(newQuery) => {
fetchResults(newQuery) // 搜索请求
},
{
immediate: true, // 首次自动执行
debounce: 300 // 防抖 300ms
}
)
实战演练一下
前端实战-Vue3-watch的五种监听情形-CSDN博客
watch
vs watchEffect
对比特性 | watch |
watchEffect |
---|---|---|
监听方式 | 需明确指定监听目标 | 自动追踪回调内的响应式依赖 |
首次执行 | 默认不执行,需 immediate: true |
默认立即执行 |
新旧值 | 提供 newVal 和 oldVal |
不提供旧值 |
适用场景 | 精确控制监听目标 | 依赖变化时自动执行逻辑 |
// watch 示例
const count = ref(0)
watch(count, (newVal, oldVal) => {
console.log(`count从${oldVal}变为${newVal}`)
})
// watchEffect 示例
const count = ref(0)
watchEffect(() => {
console.log(`当前count值: ${count.value}`) // 自动追踪count
})
// watch 处理多个依赖
const state = reactive({ a: 1, b: 2 })
watch(
[() => state.a, () => state.b],
([newA, newB], [oldA, oldB]) => {
console.log(`a: ${oldA}→${newA}, b: ${oldB}→${newB}`)
}
)
// watchEffect 处理多个依赖
const state = reactive({ a: 1, b: 2 })
watchEffect(() => {
console.log(`a=${state.a}, b=${state.b}`) // 自动追踪state.a和state.b
})
// watch 清理副作用
const id = ref(1)
const stop = watch(id, async (newId) => {
const response = await fetch(`/api/data/${newId}`)
// ...处理数据
return () => {
// 清理逻辑(如取消请求)
}
})
// watchEffect 清理副作用
const id = ref(1)
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log(`Fetching data for id: ${id.value}`)
}, 1000)
onCleanup(() => {
clearTimeout(timer)
})
})
需要知道值变化前后的差异
watch(price, (newVal, oldVal) => {
console.log(`价格变化: ${oldVal} → ${newVal}`)
})
需要惰性执行(不立即执行)
watch(userId, fetchUserData) // 只在userId变化时执行
需要精确控制监视目标
watch(
() => user.value.age,
(newAge) => { /* 只监视age */ }
)
需要自动收集多个依赖
watchEffect(() => {
// 自动追踪所有使用的响应式数据
console.log(`用户: ${user.name}, 年龄: ${user.age}`)
})
需要立即执行一次
watchEffect(initializeComponent) // 组件初始化时立即执行
需要更简洁的代码结构
watchEffect(() => {
// 所有相关逻辑在一个函数中
if (isActive.value) {
startTimer()
} else {
stopTimer()
}
})
const stop = watch(count, (newVal) => { /* ... */ })
stop() // 手动停止监听
props
的变化const props = defineProps(['userId'])
watch(() => props.userId, (newId) => {
fetchUser(newId)
})
async/await
处理异步watch(
userId,
async (newId) => {
const data = await fetchUser(newId)
console.log(data)
}
)
watch
用于精确监听数据变化,支持 ref
、reactive
、getter
和数组。deep: true
可深度监听对象/数组。immediate: true
让回调首次立即执行。watchEffect
更适合自动依赖追踪,但 watch
更可控。第一部分:TypeScript核心概念
1.自定义类型
// 自定义一个用户类型
type User = {
id: number
name: string
age?: number // 可选属性
}
// 使用自定义类型
const user: User = {
id: 1,
name: '张三'
}
2.接口
// 定义一个商品接口
interface Product {
id: number
name: string
price: number
inStock?: boolean // 可选属性
}
// 接口继承
interface DiscountedProduct extends Product {
discount: number
}
// 使用接口
const laptop: Product = {
id: 101,
name: '笔记本电脑',
price: 5999
}
3.泛型
// 通用响应类型
interface ApiResponse {
code: number
message: string
data: T
}
// 使用泛型
const userResponse: ApiResponse = {
code: 200,
message: '成功',
data: { id: 1, name: '张三' }
}
const productResponse: ApiResponse = {
code: 200,
message: '成功',
data: { id: 101, name: '笔记本电脑', price: 5999 }
}
泛型像"类型参数",让组件/函数能处理多种类型,保持类型安全。
在 Vue 组件通信中,props 是父组件向子组件传递数据的主要方式,结合 TypeScript 定义组件 Props 可以让组件更加健壮和可维护。下面我将详细介绍三种渐进式的 Props 定义方法,并解释每种方式的适用场景。
// 子组件 ChildComponent.vue
{{ props.title }}
计数: {{ props.count }}
特点:
// 子组件 ChildComponent.vue
{{ props.title }}
总数: {{ props.count }}
-
{{ item }}
特点:
?
标记// 子组件 ChildComponent.vue
{{ props.title }}
总数: {{ props.count }}
-
{{ item }}
特点:
withDefaults
特性 | 只接收值 | 接收并限制类型 | 接收+类型+默认值 |
---|---|---|---|
类型检查 | ❌ 无 | ✅ 完整 | ✅ 完整 |
默认值支持 | ❌ 无 | ❌ 无 | ✅ 支持 |
可选参数 | ❌ 隐式 | ✅ 显式(? ) |
✅ 显式(? ) |
代码复杂度 | ⭐ 最简单 | ⭐⭐ 中等 | ⭐⭐⭐ 最完整 |
适用场景 | 快速原型 | 必须参数 | 完整生产环境组件 |
withDefaults(defineProps(), {
list: () => [], // 数组
config: () => ({ enabled: false }) // 对象
})
interface User {
id: number
name: string
}
interface Props {
users: User[]
}
// 父组件