第三章 Vue3核心基础回顾:Composition API

好,咱们直接进入正题。这一章我打算带大家快速过一遍Vue3最核心的几个概念。说实话,很多同学觉得Composition API难,其实是因为没搞懂它到底解决了什么问题。

我个人习惯把Composition API看作是一种「组织代码的新思路」。以前Options API是把数据、方法、生命周期拆成不同的盒子,现在Composition API允许你把相关逻辑聚在一起。说白了,就是让代码更「内聚」。

3.1 setup:一切从这里开始

setup是Composition API的入口。组件创建之前,它会先执行。嗯,这里要注意:setup里不能使用this。为什么?因为组件实例还没创建完呢。

import { ref, onMounted } from 'vue'

export default {
  setup() {
    const count = ref(0)

    const increment = () => {
      count.value++
    }

    onMounted(() => {
      console.log('组件挂载了,当前count:', count.value)
    })

    return {
      count,
      increment
    }
  }
}

我在项目中遇到过一个问题:有同事在setup里用了async,结果模板渲染不出来。记住,setup可以是async,但组件需要配合Suspense使用。否则,你返回的是一个Promise,模板拿不到数据。

避坑指南:我曾经在setup里直接写await请求数据,然后return了结果。页面一片空白。后来才意识到,setup如果返回Promise,父组件必须用Suspense包裹。

3.2 ref vs reactive:该用哪个?

这两个API是Vue3响应式系统的基石。我经常被问到:「到底用ref还是reactive?」

我的建议很简单:

  • 基本类型(string、number、boolean):用ref
  • 对象或数组:用reactive更自然
  • 不确定未来会变成什么类型:用ref,它更灵活
import { ref, reactive } from 'vue'

// ref:包裹基本类型
const name = ref('张三')
const age = ref(25)

// reactive:包裹对象
const user = reactive({
  name: '李四',
  age: 30,
  hobbies: ['篮球', '编程']
})

// 修改ref需要.value
name.value = '王五'

// 修改reactive直接改属性
user.name = '赵六'

你想想看,ref每次都要写.value,是不是有点烦?其实Vue3的模板会自动解包,所以在模板里直接用name就行。但在setup里,必须写.value。

小技巧:我个人习惯把所有响应式数据都放在一个对象里用reactive管理。比如表单数据、页面状态。这样代码看起来更整洁,也不用纠结要不要加.value。

3.3 生命周期钩子:名字变了,用法没变

Vue3的生命周期钩子,说白了就是把Vue2的beforeDestroy改成了onBeforeUnmount,把destroyed改成了onUnmounted。其他基本一样。

Vue2 Vue3 说明
created setup 组件创建时执行
mounted onMounted DOM挂载后执行
beforeDestroy onBeforeUnmount 组件销毁前执行
destroyed onUnmounted 组件销毁后执行
import { onMounted, onUnmounted, onBeforeUnmount } from 'vue'

setup() {
  onMounted(() => {
    console.log('组件挂载了')
    // 可以在这里做DOM操作或请求数据
  })

  onBeforeUnmount(() => {
    console.log('组件即将销毁')
    // 清理定时器、取消订阅等
  })

  onUnmounted(() => {
    console.log('组件已销毁')
  })
}

我在项目中遇到过一个问题:在onMounted里注册了事件监听,但忘记在onBeforeUnmount里移除。结果组件切换后,事件还在触发,导致内存泄漏。嗯,这个坑我踩过两次才长记性。

核心要点:生命周期钩子必须在setup里同步调用。不能放在setTimeout或Promise里。否则Vue无法正确追踪。

3.4 组件通信:props/emit 和 provide/inject

组件通信是Vue里最基础也最重要的能力。我见过很多项目,父子组件通信用Vuex,其实大可不必。

3.4.1 props/emit:父子通信的标准姿势

父组件通过props传数据给子组件,子组件通过emit发事件给父组件。这是最常用、最清晰的方式。

// 父组件
<template>
  <ChildComponent 
    :title="pageTitle" 
    @update="handleUpdate" 
  />
</template>

<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'

const pageTitle = ref('首页')
const handleUpdate = (newTitle) => {
  pageTitle.value = newTitle
}
</script>
// 子组件
<template>
  <div>
    <h2>{{ title }}</h2>
    <button @click="emit('update', '新标题')">修改标题</button>
  </div>
</template>

<script setup>
defineProps({
  title: {
    type: String,
    required: true
  }
})

const emit = defineEmits(['update'])
</script>

我个人习惯在子组件里用defineProps和defineEmits,这样类型推断更友好。而且不用手动import,Vue会自动处理。

3.4.2 provide/inject:跨层级通信的利器

当组件层级很深时,一层层传props会让人崩溃。这时候provide/inject就派上用场了。

// 祖先组件
import { provide, ref } from 'vue'

const theme = ref('dark')
const updateTheme = (newTheme) => {
  theme.value = newTheme
}

provide('theme', {
  theme,
  updateTheme
})
// 后代组件
import { inject } from 'vue'

const { theme, updateTheme } = inject('theme')

// 直接使用
console.log(theme.value) // 'dark'
updateTheme('light')
注意:我曾经在项目中滥用provide/inject,导致数据流向不清晰,调试起来特别痛苦。建议只在「祖先-后代」这种跨多层场景使用。父子之间还是用props/emit更清晰。

3.5 实战中的组合技巧

好了,知识点讲完了。咱们来点实战的。我通常会把相关逻辑封装成自定义hook,这样复用性更好。

// useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  const doubleCount = computed(() => count.value * 2)

  const increment = () => count.value++
  const decrement = () => count.value--

  return {
    count,
    doubleCount,
    increment,
    decrement
  }
}
// 在组件中使用
import { useCounter } from './useCounter'

const { count, doubleCount, increment, decrement } = useCounter(10)

你想想看,这样写的好处是什么?逻辑可以复用,测试也方便。我在项目中就把用户登录状态、权限判断、主题切换这些都封装成了hook,代码瞬间清爽了很多。

我的建议:刚开始用Composition API时,别想着一步到位。先把Options API的代码迁移过来,然后慢慢拆分成hook。一口吃不成胖子,重构也是。

好,这一章就到这里。下一章咱们会深入探讨Vue3的响应式原理,看看ref和reactive背后到底是怎么工作的。到时候我会用一些实际案例,帮大家彻底搞懂「响应式」这三个字。