vue3新增的api - 完整解决方案与实战教程

在Vue 2时代,我们习惯用Options API组织代码,逻辑分散在data、methods、computed、watch中。当项目升级到Vue 3后,团队开始尝试Composition API中的新API,比如ref、reactive、toRefs、watchEffect、shallowRef、markRaw等。但很多开发者直接照搬Vue 2的思维,结果踩坑不断:响应式丢失、watchEffect无限循环、shallowRef与reactive混用导致视图不更新、模板ref与响应式ref混淆、以及setup中访问this为undefined。这些痛点轻则导致页面数据不刷新,重则引发内存泄漏和难以定位的性能问题。本文从真实业务场景出发,逐一拆解Vue 3新增API的常见陷阱,并给出可直接落地的完整代码方案。

问题现象:升级Vue 3后,数据变了视图不动

典型现象包括:

原因分析:Vue 3响应式系统的底层逻辑变了

Vue 3使用Proxy替代Object.defineProperty,响应式追踪基于代理对象的属性访问。以下是最容易出错的几个原因:

  1. reactive解构丢失响应性:解构相当于把属性值复制给新变量,新变量不再经过Proxy,因此失去追踪能力。必须用toRefstoRef保持引用。
  2. 整体替换reactive对象state = reactive({...})后直接state = newObj会切断原代理,应使用Object.assignref
  3. watchEffect自动收集依赖:如果回调中修改了被读取的响应式数据,会再次触发回调,形成死循环。需用watch显式指定源,或加条件判断。
  4. shallowRef只追踪.value整体替换:修改shallowRef内部对象的属性不会触发更新,必须整体替换.value
  5. 模板ref与响应式ref命名冲突:Vue 3中ref既用于响应式数据又用于模板引用,若变量名与模板ref属性同名,setup中返回的ref会覆盖模板引用。

解决方案(附完整代码)

下面通过一个“用户列表+搜索+分页”的实战组件,演示如何正确使用Vue 3新增API并避开上述坑。

<template>
  <div>
    <!-- 模板ref:注意变量名不要与响应式ref重名 -->
    <input ref="searchInputRef" v-model="keyword" />
    <ul>
      <li v-for="user in userList" :key="user.id">{{ user.name }}</li>
    </ul>
    <button @click="loadMore">加载更多</button>
  </div>
</template>

<script setup>
import { ref, reactive, toRefs, watch, watchEffect, shallowRef, markRaw, onMounted } from 'vue'

// 1. 基础响应式:ref用于基本类型,reactive用于对象
const keyword = ref('')
const page = ref(1)

// 2. reactive对象解构必须用toRefs,否则丢失响应性
const state = reactive({
  loading: false,
  total: 0,
  list: []
})
// 正确:解构后仍是ref,模板中自动解包
const { loading, total, list } = toRefs(state)

// 3. 整体替换reactive对象:用Object.assign或ref
// 错误示范:state = reactive({ ... }) 会切断原代理
// 正确示范:
function resetState() {
  Object.assign(state, {
    loading: false,
    total: 0,
    list: []
  })
}

// 4. shallowRef:只追踪.value整体替换,适合大数组性能优化
const bigData = shallowRef([])
// 错误:bigData.value.push(item) 不会触发更新
// 正确:bigData.value = [...bigData.value, item]

// 5. markRaw:标记永不响应式的对象,避免性能开销
const staticConfig = markRaw({
  api: '/api/user',
  pageSize: 20
})

// 6. watchEffect:自动收集依赖,但避免修改依赖自身
watchEffect(() => {
  // 只读取keyword,不修改它,否则死循环
  console.log('keyword changed:', keyword.value)
  // 若需要修改,用watch显式监听
})

// 7. watch:显式指定源,可拿到新旧值,适合异步请求
watch(page, async (newPage) => {
  state.loading = true
  try {
    const res = await fetch(`${staticConfig.api}?page=${newPage}&size=${staticConfig.pageSize}`)
    const data = await res.json()
    // 注意:这里直接修改state.list,因为state是reactive
    state.list = data.list
    state.total = data.total
  } finally {
    state.loading = false
  }
})

// 8. 模板ref:必须与响应式ref区分命名
const searchInputRef = ref(null)
onMounted(() => {
  // 正确获取DOM
  searchInputRef.value?.focus()
})

// 9. 模拟加载更多:用shallowRef整体替换
function loadMore() {
  const newItem = { id: Date.now(), name: `User ${Date.now()}` }
  // 错误:bigData.value.push(newItem)
  bigData.value = [...bigData.value, newItem]
}

// 10. 暴露给模板
const userList = list
</script>

排查步骤与最佳实践:

掌握这些新增API的边界条件后,Vue 3的Composition API能带来更清晰的逻辑复用和更好的性能。建议在团队内建立代码规范,强制使用toRefs解构、禁止直接替换reactive对象,并配合ESLint插件eslint-plugin-vuevue/no-ref-as-operand等规则,从工具层面规避低级错误。