最近在重构一个老移动端项目,用Vue3重写的过程中,发现了一些平时不太注意的API,但用起来真的很香。今天分享几个我觉得特别实用的,希望能帮到正在学习Vue3的jym。
1. shallowRef 和 shallowReactive - '大数据'性能提升
先说个场景:你有一个很大的对象,里面嵌套了很多层数据,但你只需要最外层的变化触发更新。这时候用 ref 或 reactive 就会把所有嵌套都变成响应式的,性能开销很大。
javascript
import { ref, shallowRef, reactive, shallowReactive, triggerRef } from 'vue'
// 普通 ref - 深度响应式
const deepData = ref({
user: {
profile: {
name: '张三',
address: {
city: '北京',
// ... 还有很多层
}
}
}
})
// shallowRef - 浅层响应式,只监听第一层
const shallowData = shallowRef({
user: {
profile: {
name: '李四'
}
}
})
// 只有直接替换整个对象才会触发更新
shallowData.value = { user: { profile: { name: '王五' } } } // ✅ 会触发更新
// 修改内部属性不会触发更新
shallowData.value.user.profile.name = '赵六' // ❌ 不会触发更新
// 但可以用 triggerRef 手动触发更新
shallowData.value.user.profile.name = '赵六'
triggerRef(shallowData) // ✅ 手动触发更新,视图会刷新
// shallowReactive - 浅层响应式的 reactive 版本
const shallowState = shallowReactive({
user: {
name: '张三',
age: 20
}
})
shallowState.user = { name: '李四', age: 25 } // ✅ 会触发更新
shallowState.user.name = '王五' // ❌ 不会触发更新
我在处理表格数据的时候经常用这个,特别是那种几千行的数据,用 shallowRef 能明显感觉到性能提升。shallowReactive 和 shallowRef 的区别在于,前者直接返回响应式对象,后者返回的是包装后的 ref。
triggerRef 的使用场景 :当你使用 shallowRef 时,如果确实需要修改嵌套属性并触发更新,但又不想替换整个对象(可能很耗性能),就可以先修改属性,然后调用 triggerRef 手动触发更新。这样既享受了浅层响应式的性能优势,又能在需要时精确控制更新时机。
javascript
// 实际应用:批量修改后统一触发更新
const tableData = shallowRef([
{ id: 1, name: '张三', status: 'active' },
{ id: 2, name: '李四', status: 'active' },
// ... 几千条数据
])
// 批量修改多个嵌套属性
tableData.value.forEach(item => {
item.status = 'inactive' // 修改嵌套属性
item.updatedAt = Date.now() // 继续修改
})
// 所有修改完成后,统一触发一次更新(性能更好)
triggerRef(tableData) // ✅ 只触发一次更新,而不是每条数据都触发
2. markRaw - 让某些数据"免疫"响应式
有些数据你明确知道不需要响应式,比如第三方库的实例、DOM元素、或者一些配置对象。用 markRaw 标记后,Vue就不会把它变成响应式的。
javascript
import { reactive, markRaw } from 'vue'
const state = reactive({
// 这个第三方库实例不需要响应式
chart: markRaw(new ChartLibrary()),
// 这个配置对象也不需要
config: markRaw({
apiUrl: 'https://api.example.com',
timeout: 5000
}),
// 这个需要响应式
count: 0
})
// 即使把 chart 放到 reactive 里,它也不会变成响应式
// 这样既避免了不必要的性能开销,也防止了某些库因为响应式代理而出错
我之前用 ECharts 的时候就遇到过这个问题,图表实例被响应式代理后各种报错,用 markRaw 一包就解决了。
3. toRaw - 获取原始对象
有时候你需要拿到响应式对象的原始值,比如传给第三方库、或者做深拷贝。toRaw 就是干这个的。
javascript
import { reactive, toRaw } from 'vue'
const state = reactive({
name: 'Vue3',
version: '3.0'
})
// 获取原始对象,不再是 Proxy
const rawState = toRaw(state)
// 传给需要普通对象的函数
someThirdPartyLibrary.process(rawState)
// 或者做深拷贝
const copy = JSON.parse(JSON.stringify(toRaw(state)))
注意,toRaw 只能拿到一层,如果对象里还有嵌套的响应式对象,那些还是 Proxy。需要递归处理的话,可以配合 markRaw 使用。
4. customRef - 自定义响应式引用
这个API特别适合做防抖、节流,或者需要自定义 get/set 逻辑的场景。
javascript
import { customRef } from 'vue'
// 防抖的 ref
function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track() // 追踪依赖
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 触发更新
}, delay)
}
}
})
}
// 使用
const searchText = useDebouncedRef('')
// 用户输入时不会立即更新,等停止输入 200ms 后才更新
我在搜索框里经常用这个,避免用户每输入一个字符就发一次请求。
5. readonly - 创建只读的响应式对象
有时候你想让某些数据是响应式的(能追踪变化),但不允许修改。readonly 就派上用场了。
javascript
import { reactive, readonly } from 'vue'
const original = reactive({
count: 0,
name: 'Vue3'
})
// 创建只读版本
const readOnlyData = readonly(original)
// 可以读取
console.log(readOnlyData.count) // 0
// 不能修改(开发环境会警告)
readOnlyData.count = 1 // ⚠️ 警告:Set operation on key "count" failed
// 但修改原始对象,只读版本也会更新
original.count = 1
console.log(readOnlyData.count) // 1 ✅
这个在组件间传递 props 的时候很有用,特别是你想确保子组件不会意外修改父组件的数据。
6. getCurrentInstance - 获取当前组件实例
这个API在组合式API里获取组件实例特别方便,虽然官方文档说主要给库开发者用,但实际开发中偶尔也会用到。
javascript
import { getCurrentInstance } from 'vue'
export default {
setup() {
const instance = getCurrentInstance()
// 获取组件的 props
console.log(instance.props)
// 获取组件的 attrs
console.log(instance.attrs)
// 获取组件的 emit
instance.emit('custom-event')
// 访问全局属性
const $router = instance.appContext.config.globalProperties.$router
return {}
}
}
不过要注意,getCurrentInstance 只能在 setup 或生命周期钩子里调用,而且不能缓存到异步回调里用。
7. defineAsyncComponent - 异步组件加载
这个其实不算特别冷门,但很多人可能没深入用过。除了基本的懒加载,它还有很多高级用法。
javascript
import { defineAsyncComponent } from 'vue'
// 基础用法
const AsyncComponent = defineAsyncComponent(() => import('./HeavyComponent.vue'))
// 带加载状态和错误处理
const AsyncComponentWithStates = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
loadingComponent: LoadingSpinner, // 加载时显示的组件
errorComponent: ErrorComponent, // 错误时显示的组件
delay: 200, // 延迟显示 loading,避免闪烁
timeout: 3000, // 超时时间
onError(error, retry, fail, attempts) {
// 错误处理逻辑
if (attempts <= 3) {
retry() // 重试
} else {
fail() // 放弃
}
}
})
我在做路由懒加载和按需加载大组件的时候,这个API帮了不少忙。
8. watchEffect 的立即执行和清理
watchEffect 大家都知道,但它的立即执行特性和清理函数可能很多人没充分利用。
javascript
import { watchEffect, ref } from 'vue'
const count = ref(0)
const stop = watchEffect((onInvalidate) => {
// 这个函数会立即执行一次
console.log('count:', count.value)
// 清理函数:在下次执行前或停止监听时调用
onInvalidate(() => {
console.log('清理之前的副作用')
// 比如取消请求、清除定时器等
})
})
// 手动停止监听
stop()
这个特性在处理副作用的时候特别有用,比如监听数据变化自动发送请求,可以在清理函数里取消之前的请求,避免竞态条件。
javascript
import { watchEffect, ref } from 'vue'
const userId = ref(1)
let abortController = null
watchEffect((onInvalidate) => {
// 取消之前的请求
if (abortController) {
abortController.abort()
}
// 创建新的请求
abortController = new AbortController()
fetch(`/api/user/${userId.value}`, {
signal: abortController.signal
})
.then(res => res.json())
.then(data => {
// 处理数据
})
// 清理函数:在下次执行前或组件卸载时调用
onInvalidate(() => {
if (abortController) {
abortController.abort()
}
})
})
这样就能避免快速切换用户ID时,旧的请求覆盖新请求结果的问题。
9. unref、isRef、isReactive - 类型检查工具
这些工具函数在处理可能是响应式也可能不是的数据时特别有用。
javascript
import { ref, reactive, unref, isRef, isReactive, isReadonly } from 'vue'
// unref - 如果是 ref 就返回 .value,否则返回原值
function useValue(maybeRef) {
return unref(maybeRef) // 不用判断是不是 ref,直接 unref 就行
}
const count = ref(10)
const name = 'Vue3'
console.log(useValue(count)) // 10
console.log(useValue(name)) // 'Vue3'
// isRef - 判断是否是 ref
if (isRef(count)) {
console.log('这是一个 ref')
}
// isReactive - 判断是否是 reactive 对象
const state = reactive({ count: 0 })
if (isReactive(state)) {
console.log('这是一个 reactive 对象')
}
// isReadonly - 判断是否是 readonly 对象
const readonlyState = readonly(state)
if (isReadonly(readonlyState)) {
console.log('这是一个 readonly 对象')
}
在写工具函数或者组合式函数的时候,这些检查函数能让你写出更健壮的代码。
10. effectScope 和 onScopeDispose - 批量管理副作用
effectScope 是 Vue3.2 新增的,可以批量管理一组响应式效果(computed、watch、watchEffect等),统一停止或清理。
javascript
import { effectScope, ref, watchEffect, computed, onScopeDispose } from 'vue'
function useMouse() {
const x = ref(0)
const y = ref(0)
// 创建一个作用域
const scope = effectScope()
scope.run(() => {
// 在这个作用域内的所有响应式效果都会被统一管理
watchEffect(() => {
const handler = (e) => {
x.value = e.clientX
y.value = e.clientY
}
window.addEventListener('mousemove', handler)
// 清理函数
onScopeDispose(() => {
window.removeEventListener('mousemove', handler)
})
})
// 也可以有多个 watchEffect、computed 等
const distance = computed(() => {
return Math.sqrt(x.value ** 2 + y.value ** 2)
})
})
// 返回停止函数
return {
x,
y,
stop: () => scope.stop() // 一次性停止所有效果
}
}
这个在写组合式函数的时候特别有用,可以确保所有副作用都能被正确清理,避免内存泄漏。
11. provide/inject 的响应式传递
provide/inject 大家都知道,但很多人不知道它可以传递响应式数据,而且子组件可以直接修改。
javascript
// 父组件
import { provide, ref, reactive } from 'vue'
export default {
setup() {
const count = ref(0)
const user = reactive({ name: '张三', age: 20 })
// 提供响应式数据
provide('count', count)
provide('user', user)
// 也可以提供修改函数
provide('increment', () => {
count.value++
})
return { count, user }
}
}
// 子组件(可以是任意深度的子组件)
import { inject, ref } from 'vue'
export default {
setup() {
// 注入响应式数据,可以设置默认值
const count = inject('count', ref(0)) // 如果父组件没有提供,使用默认值
const user = inject('user', () => ({ name: '默认用户', age: 0 })) // 也可以使用工厂函数
const increment = inject('increment', () => {
console.warn('increment 方法未提供')
})
// 可以直接修改(如果父组件提供的是响应式数据)
const handleClick = () => {
count.value++ // ✅ 父组件的 count 也会更新
user.name = '李四' // ✅ 父组件的 user 也会更新
// 或者调用父组件提供的方法
increment()
}
return { count, user, handleClick }
}
}
这个在跨层级组件通信的时候特别方便,比 props 一层层传要优雅得多。
12. v-memo - 性能优化的指令
v-memo 是 Vue3.2 新增的指令,可以缓存模板的一部分,只有在依赖项变化时才重新渲染。
vue
<template>
<!-- 只有 item.id 或 selected 变化时才重新渲染这个 div -->
<div v-for="item in list" :key="item.id" v-memo="[item.id, selected]">
<p>{{ item.name }}</p>
<p>{{ item.description }}</p>
<!-- 复杂的内容 -->
<ExpensiveComponent :data="item.data" />
</div>
</template>
<script setup>
import { ref } from 'vue'
const list = ref([
{ id: 1, name: 'Item 1', description: '...', data: {...} },
{ id: 2, name: 'Item 2', description: '...', data: {...} },
// ...
])
const selected = ref(null)
</script>
这个在处理长列表的时候特别有用,可以大幅提升性能。不过要注意,v-memo 必须和 v-for 一起使用。
13. Suspense - 异步组件的加载状态
Suspense 可以优雅地处理异步组件的加载状态,不需要在每个组件里单独处理。
vue
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
</script>
如果异步组件内部有异步操作(比如 setup 里用了 await),Suspense 也会等待:
vue
<!-- AsyncComponent.vue -->
<script setup>
// 这个组件内部有异步操作
const data = await fetchData() // Suspense 会等待这个完成
</script>
这个在处理异步数据加载的时候特别优雅,比手动管理 loading 状态要方便。
14. 模板引用的高级用法
模板引用(ref)除了获取 DOM 元素,还有很多高级用法。
vue
<template>
<!-- 在 v-for 中获取多个元素 -->
<div v-for="item in list" :ref="el => setItemRef(el, item.id)">
{{ item.name }}
</div>
<!-- 获取子组件实例 -->
<ChildComponent ref="childRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const list = ref([
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
])
// 存储多个元素引用
const itemRefs = ref({})
const setItemRef = (el, id) => {
if (el) {
itemRefs.value[id] = el
}
}
// 子组件引用
const childRef = ref(null)
onMounted(() => {
// 访问子组件的方法或属性
console.log(childRef.value.someMethod())
console.log(childRef.value.someProperty)
// 访问所有列表项的 DOM
console.log(itemRefs.value)
})
</script>
还可以配合 defineExpose 暴露子组件的特定内容:
vue
<!-- ChildComponent.vue -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
const privateData = ref('private') // 这个不会被暴露
const increment = () => {
count.value++
}
// 只暴露 count 和 increment
defineExpose({
count,
increment
})
</script>
15. computed 的 getter/setter 形式
computed 除了只读形式,还可以有 getter 和 setter,实现双向绑定。
javascript
import { ref, computed } from 'vue'
const firstName = ref('张')
const lastName = ref('三')
// 可读写的 computed
const fullName = computed({
get() {
return `${firstName.value}${lastName.value}`
},
set(newValue) {
// 当设置 fullName 时,自动拆分到 firstName 和 lastName
const parts = newValue.split('')
if (parts.length >= 2) {
firstName.value = parts[0]
lastName.value = parts.slice(1).join('')
}
}
})
// 使用
console.log(fullName.value) // '张三'
fullName.value = '李四' // 自动更新 firstName 和 lastName
console.log(firstName.value) // '李'
console.log(lastName.value) // '四'
这个在处理表单数据转换的时候特别有用。
16. watch 的深度监听和立即执行
watch 的选项参数有很多实用的配置。
javascript
import { watch, ref, reactive } from 'vue'
const count = ref(0)
const state = reactive({
user: {
name: '张三',
age: 20
}
})
// 深度监听对象
watch(
() => state.user,
(newVal, oldVal) => {
console.log('user 变化了', newVal, oldVal)
},
{ deep: true } // 深度监听嵌套属性
)
// 或者直接监听整个对象
watch(
state,
(newVal, oldVal) => {
// 注意:newVal 和 oldVal 是同一个对象(因为 reactive)
console.log('state 变化了')
},
{ deep: true }
)
// 立即执行一次
watch(
count,
(newVal, oldVal) => {
console.log('count:', newVal)
},
{ immediate: true } // 立即执行一次回调
)
// 监听多个源
watch(
[count, () => state.user.name],
([newCount, newName], [oldCount, oldName]) => {
console.log('count 或 name 变化了')
}
)
// 手动实现只监听一次的效果
let stopWatcher = null
stopWatcher = watch(
count,
(newVal) => {
console.log('只执行一次')
// 执行后立即停止监听
if (stopWatcher) {
stopWatcher()
stopWatcher = null
}
}
)
17. nextTick 的多种用法
nextTick 大家都知道,但它的使用场景可能很多人没完全掌握。
javascript
import { nextTick, ref } from 'vue'
const count = ref(0)
const message = ref('')
// 等待 DOM 更新后执行
async function handleClick() {
count.value++
message.value = 'Count 已更新'
// 此时 DOM 可能还没更新
console.log(document.querySelector('.count').textContent) // 可能是旧值
// 等待 DOM 更新
await nextTick()
console.log(document.querySelector('.count').textContent) // 新值
// 或者用回调形式
nextTick(() => {
console.log('DOM 已更新')
})
}
// 在组件挂载后操作 DOM
import { onMounted } from 'vue'
onMounted(async () => {
// 即使组件已挂载,某些 DOM 操作可能还需要等待
await nextTick()
// 现在可以安全地操作 DOM 了
document.querySelector('.some-element').focus()
})
18. 响应式 API 的转换工具
Vue3 提供了一些转换工具,可以在不同响应式类型之间转换。
javascript
import { ref, reactive, toRef, toRefs, isRef } from 'vue'
const state = reactive({
name: 'Vue3',
version: '3.0',
count: 0
})
// toRef - 将 reactive 对象的某个属性转为 ref
const nameRef = toRef(state, 'name')
nameRef.value = 'Vue 3' // 会同步更新 state.name
// toRefs - 将 reactive 对象的所有属性转为 ref
const { name, version, count } = toRefs(state)
// 现在 name、version、count 都是 ref,可以在模板中直接使用
// 在组合式函数中返回响应式数据时特别有用
function useCounter() {
const state = reactive({
count: 0,
step: 1
})
const increment = () => {
state.count += state.step
}
// 返回 toRefs,让使用者可以解构而不失去响应式
return {
...toRefs(state),
increment
}
}
// 使用时可以解构
const { count, step, increment } = useCounter()
// count 和 step 仍然是响应式的
19. 自定义指令的高级用法
自定义指令除了基本的 mounted 和 updated,还有很多生命周期钩子。
javascript
// 自定义指令直接定义对象即可,不需要特殊导入
const vMyDirective = {
// 元素插入 DOM 前调用
created(el, binding, vnode) {
console.log('created')
},
// 元素插入 DOM 后调用
mounted(el, binding, vnode, prevVnode) {
console.log('mounted', binding.value)
// binding.value - 指令的值
// binding.oldValue - 之前的值(仅在 update 和 componentUpdated 中可用)
// binding.arg - 指令参数,如 v-my-directive:foo 中的 'foo'
// binding.modifiers - 修饰符对象,如 v-my-directive.foo.bar 中的 { foo: true, bar: true }
},
// 在包含组件的 VNode 更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {
console.log('beforeUpdate')
},
// 在包含组件的 VNode 及其子 VNode 更新后调用
updated(el, binding, vnode, prevVnode) {
console.log('updated')
},
// 在卸载前调用
beforeUnmount(el, binding, vnode) {
console.log('beforeUnmount')
},
// 卸载后调用
unmounted(el, binding, vnode) {
console.log('unmounted')
}
}
// 使用
// <div v-my-directive:arg.modifier="value"></div>
实际应用:实现一个点击外部关闭的指令
javascript
const vClickOutside = {
mounted(el, binding) {
el._clickOutside = (event) => {
if (!(el === event.target || el.contains(event.target))) {
binding.value() // 调用传入的函数
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
}
}
20. 组合式 API 的类型推断技巧
在 TypeScript 中使用组合式 API 时,有一些类型推断的技巧。
typescript
import { ref, computed, Ref, ComputedRef } from 'vue'
// 明确指定 ref 的类型
const count: Ref<number> = ref(0)
// 或者让 TypeScript 自动推断
const count = ref<number>(0)
// computed 的类型
const doubleCount: ComputedRef<number> = computed(() => count.value * 2)
// 在组合式函数中返回类型
function useCounter(initialValue: number = 0) {
const count = ref(initialValue)
const increment = () => {
count.value++
}
return {
count: count as Ref<number>, // 或者用 readonly
increment
}
}
// 使用泛型
function useAsyncData<T>(url: string) {
const data = ref<T | null>(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const fetchData = async () => {
loading.value = true
try {
const response = await fetch(url)
data.value = await response.json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
return {
data: readonly(data),
loading: readonly(loading),
error: readonly(error),
fetchData
}
}
21. 性能优化的组合拳
把这些 API 组合起来,可以实现更强大的性能优化。
javascript
import { shallowRef, markRaw, computed, watchEffect } from 'vue'
// 场景:处理大量数据列表
function useLargeList(initialData) {
// 用 shallowRef 避免深度响应式
const items = shallowRef(initialData.map(item => ({
...item,
// 某些不需要响应式的数据用 markRaw
metadata: markRaw(item.metadata)
})))
// 用 computed 缓存计算结果
const filteredItems = computed(() => {
return items.value.filter(item => item.visible)
})
// 用 watchEffect 处理副作用,自动清理
const stop = watchEffect((onInvalidate) => {
// 处理过滤后的数据
processItems(filteredItems.value)
onInvalidate(() => {
// 清理工作
cleanup()
})
})
return {
items,
filteredItems,
stop
}
}
22. 实际项目中的最佳实践
结合我自己的项目经验,分享几个实用的模式:
模式1:响应式数据的初始化
javascript
import { ref, reactive, onMounted, readonly } from 'vue'
function useUserData(userId) {
const user = ref(null)
const loading = ref(false)
const error = ref(null)
const fetchUser = async () => {
loading.value = true
error.value = null
try {
const response = await fetch(`/api/users/${userId}`)
user.value = await response.json()
} catch (e) {
error.value = e
} finally {
loading.value = false
}
}
// 组件挂载时自动获取
onMounted(fetchUser)
return {
user: readonly(user),
loading: readonly(loading),
error: readonly(error),
refetch: fetchUser
}
}
模式2:表单验证
javascript
import { ref, computed, reactive, readonly } from 'vue'
function useFormValidation() {
const form = reactive({
email: '',
password: '',
confirmPassword: ''
})
const errors = reactive({})
const validateEmail = (email) => {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
// 用 computed 实时验证
const isValid = computed(() => {
errors.email = form.email && !validateEmail(form.email)
? '邮箱格式不正确'
: ''
errors.password = form.password.length < 6
? '密码至少6位'
: ''
errors.confirmPassword = form.password !== form.confirmPassword
? '两次密码不一致'
: ''
return !Object.values(errors).some(err => err)
})
return {
form,
errors: readonly(errors),
isValid
}
}
模式3:防抖和节流的组合使用
javascript
import { customRef, watch } from 'vue'
// 防抖 ref - 基于 customRef 实现
function useDebounceRef(value, delay = 300) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
// 节流函数 - 配合 watch 使用(这是纯 JS 函数,不是 Vue3 API,但经常和 Vue3 一起用)
function useThrottleFn(fn, delay = 300) {
let lastTime = 0
return function(...args) {
const now = Date.now()
if (now - lastTime >= delay) {
fn.apply(this, args)
lastTime = now
}
}
}
// 使用
const searchText = useDebounceRef('')
watch(searchText, (newVal) => {
// 搜索逻辑,只在用户停止输入 300ms 后执行
performSearch(newVal)
})
const handleScroll = useThrottleFn(() => {
// 滚动处理,每 300ms 最多执行一次
updateScrollPosition()
}, 100)
版本要求说明
部分 API 需要特定版本的 Vue3:
- Vue 3.2+ :
effectScope、onScopeDispose、v-memo - Vue 3.0+:其他大部分 API
建议使用 Vue 3.2 或更高版本,以获得完整的 API 支持。
常见陷阱和注意事项
-
shallowRef/shallowReactive 的陷阱:修改嵌套属性不会触发更新,如果需要更新,要替换整个对象。
-
toRaw 的局限性:只能获取一层的原始对象,嵌套的响应式对象仍然是 Proxy。
-
getCurrentInstance 的使用限制 :只能在
setup或生命周期钩子中调用,不能缓存到异步回调中使用。 -
inject 的默认值 :如果父组件没有提供值,
inject会返回undefined,建议总是提供默认值。 -
watchEffect 的依赖追踪:会自动追踪所有在函数内访问的响应式数据,注意不要访问不需要追踪的数据。
-
computed 的副作用 :不要在
computed中执行副作用操作(如修改 DOM、发送请求等),应该使用watch或watchEffect。
总结下
有些API你可能不常用,但在特定场景下真的能解决实际问题。性能优化、边界情况处理、自定义逻辑,这些场景下它们都能派上用场。
不过也要注意,不要过度使用。比如 shallowRef 虽然性能好,但如果你确实需要深度响应式,就不要为了性能而牺牲功能。工具是死的,场景是活的,根据实际需求选择最合适的方案才是王道。。。