Vue3 Composition API 最佳实践,别再乱写 setup 了

从 Options API 转到 Composition API 半年了,踩了不少坑,也总结了一些写法。分享一下我们团队最终沉淀下来的规范。

为什么用 Composition API

Options API 的问题:同一个功能的代码分散在 datacomputedmethodswatch 里。一个搜索功能要跳四个地方看代码。

Composition API 的核心价值是按功能组织代码,而不是按选项类型:

vue 复制代码
<script setup>
// 搜索相关的全在这里
const keyword = ref('')
const results = ref([])
const loading = ref(false)

const debouncedSearch = useDebounceFn((kw) => {
  loading.value = true
  searchApi(kw).then(res => {
    results.value = res.data
    loading.value = false
  })
}, 300)

watch(keyword, (val) => debouncedSearch(val))

// 分页相关的全在这里
const currentPage = ref(1)
const pageSize = ref(20)
const total = ref(0)
</script>

ref vs reactive

ts 复制代码
// ref:适合基本类型和需要整体替换的引用类型
const count = ref(0)
const user = ref<User | null>(null)

// reactive:适合对象且不需要整体替换的场景
const form = reactive({
  username: '',
  email: '',
  role: 'USER'
})

我们团队的约定:优先用 ref,只有明确不需要整体替换的表单/配置对象才用 reactive。原因是 reactive 解构会丢失响应性,新手容易踩坑。

ts 复制代码
// 踩坑:解构丢失响应性
const form = reactive({ name: '', age: 0 })
let { name } = form  // name 是普通字符串,不是响应式
name = 'Alice'       // form.name 不会变

// 正确:用 toRefs
const { name, age } = toRefs(form)

自定义 Hook(composable)

把可复用的逻辑抽成 composable,命名以 use 开头:

ts 复制代码
// composables/useLoading.ts
export function useLoading<T>(asyncFn: () => Promise<T>) {
  const loading = ref(false)
  const error = ref<Error | null>(null)
  const data = ref<T | null>(null) as Ref<T | null>

  async function execute() {
    loading.value = true
    error.value = null
    try {
      data.value = await asyncFn()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  return { loading, error, data, execute }
}

// 使用
const { loading, error, data, execute } = useLoading(() => fetchUserList())
onMounted(execute)
ts 复制代码
// composables/usePagination.ts
export function usePagination<T>(fetchFn: (page: number, size: number) => Promise<T[]>) {
  const list = ref<T[]>([]) as Ref<T[]>
  const currentPage = ref(1)
  const pageSize = ref(20)
  const total = ref(0)
  const loading = ref(false)

  async function load() {
    loading.value = true
    try {
      const [data, count] = await Promise.all([
        fetchFn(currentPage.value, pageSize.value),
        // 假设接口返回 [数据, 总数]
      ])
      list.value = data
      total.value = count
    } finally {
      loading.value = false
    }
  }

  function changePage(page: number) {
    currentPage.value = page
    load()
  }

  onMounted(load)

  return { list, currentPage, pageSize, total, loading, changePage }
}

watch 和 watchEffect 的区别

ts 复制代码
// watch:明确指定监听谁,有新旧值
watch(keyword, (newVal, oldVal) => {
  console.log(`${oldVal} → ${newVal}`)
  search(newVal)
})

// watchEffect:自动收集依赖,没有参数
watchEffect(() => {
  // 里面用到的响应式数据都会被追踪
  search(keyword.value)
})

建议 :大部分场景用 watch,因为依赖关系明确、好调试。watchEffect 适合"初始化时自动跑一次"的场景。

生命周期

ts 复制代码
import { onMounted, onUnmounted, onBeforeMount } from 'vue'

onMounted(() => {
  console.log('组件挂载完成')
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  // 一定要清理!
  window.removeEventListener('resize', handleResize)
})

onUnmounted 里清理定时器、事件监听、WebSocket 连接。不清理就是内存泄漏。

defineExpose

<script setup> 默认是封闭的,父组件通过 ref 访问子组件时拿不到任何东西。需要暴露接口:

vue 复制代码
<!-- 子组件 -->
<script setup>
const inputRef = ref<HTMLInputElement>()

function focus() {
  inputRef.value?.focus()
}

function clear() {
  // ...
}

// 只暴露 focus 和 clear
defineExpose({ focus, clear })
</script>

总结

Composition API 的核心原则:按功能组织代码、composable 复用逻辑、优先用 ref、watch 优于 watchEffect、生命周期里记得清理。把这些规范定好,团队代码风格就统一了。

相关推荐
+VX:Fegn08952 小时前
计算机毕业设计|基于java+ vue共享单车信息系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
Bs_MoneyMagnet3 小时前
基于springboot+vue的非遗物质文化遗产系统的设计与实现 源码+文档
java·vue.js·spring boot·后端·spring·毕业设计·计算机毕业设计
计算机毕设定制辅导-无忧学长3 小时前
《基于SpringBoot的未成年人健康知识科普平台的设计与实现》
java·开发语言·vue.js·spring boot·未成年人健康知识科普平台
专业程序开发源5 小时前
springboot篮球联赛管理系统13635-计算机课程设计、毕业设计
vue.js·spring boot·后端·python·django·php·课程设计
一 乐6 小时前
自习室座位预约管理系统|基于springboot + vue自习室座位预约管理系统(源码+数据库+文档)
java·vue.js·spring boot
晚安日记wanna7 小时前
Vue2 的 defineProperty 差在哪四层追问筛掉九成候选人
前端·vue.js·面试
dy171719 小时前
vue2树节点刷新后定位到上一节点展开
vue.js
高级程序源1 天前
django大学生创新创业项目管理系统94923-计算机课程设计、毕业设计
javascript·vue.js·spring boot·后端·python·django·课程设计
VXbishe1 天前
【课程设计】基于SpringBoot的乡村政务服务系统的设计与实现-计算机毕设77011
javascript·vue.js·spring boot·python·php·课程设计·政务