Vue 3 组合式 API 深度解析:从 setup 到响应式原理的完整指南

Vue 3 的发布带来了许多令人兴奋的新特性,其中最引人注目的莫过于组合式 API(Composition API)。它从根本上改变了我们组织组件逻辑的方式,让代码复用和类型推导变得更加自然。本文将带你从 setup 函数出发,逐步深入响应式系统的底层原理,最后通过实战案例展示如何优雅地使用组合式 API。

为什么需要组合式 API?

在 Vue 2 中,我们使用选项式 API(Options API)来组织代码:data 放数据、methods 放方法、computed 放计算属性。当组件变得复杂时,一个功能的逻辑会被分散到多个选项中,导致代码难以阅读和维护。

组合式 API 允许我们按逻辑功能组织代码,而不是按选项类型。所有与某个功能相关的响应式数据、计算属性、方法和生命周期钩子都可以写在一起,甚至可以抽离到独立的组合函数中。

setup 函数:组合式 API 的入口

setup 是组合式 API 的起点,它在组件实例创建之前执行,因此无法访问 this。它接收两个参数:

  • props:组件的响应式 props 对象
  • context:包含 attrsslotsemitexpose 的上下文对象
import { ref, computed } from 'vue'

export default {
  props: {
    initialCount: { type: Number, default: 0 }
  },
  setup(props, { emit }) {
    const count = ref(props.initialCount)
    const double = computed(() => count.value * 2)

    function increment() {
      count.value++
      emit('change', count.value)
    }

    return { count, double, increment }
  }
}

需要注意的是,setup 返回的对象中的属性会自动暴露给模板使用。如果使用 <script setup> 语法糖,则可以省略显式的 return,代码更加简洁。

响应式核心:ref 与 reactive

Vue 3 提供了两个创建响应式数据的 API:

ref:包装任意值

ref 接收一个内部值并返回一个响应式的、可变的 ref 对象,该对象只有一个 .value 属性指向内部值。

import { ref } from 'vue'

const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1

在模板中,Vue 会自动解包 ref,因此可以直接写 {{ count }} 而不需要 .value

reactive:代理对象

reactive 返回一个对象的响应式代理,它基于 ES6 的 Proxy 实现,可以深度监听对象属性的变化。

import { reactive } from 'vue'

const state = reactive({
  name: 'Vue 3',
  version: 3
})

state.version = 3.4 // 触发更新

选择建议:基本类型用 ref,对象类型两者皆可,但 reactive 在解构时会丢失响应性,此时应配合 toRefs 使用。

响应式原理:Proxy 与依赖收集

Vue 3 响应式系统的核心可以用一句话概括:在读取时收集依赖,在修改时触发更新

Proxy 代理

与 Vue 2 使用 Object.defineProperty 不同,Vue 3 使用 Proxy 来拦截对象的操作。Proxy 可以拦截多达 13 种操作,包括属性读取、赋值、删除、in 操作符等,并且能完美支持数组索引和 MapSet 等集合类型。

const target = { count: 0 }
const proxy = new Proxy(target, {
  get(target, key, receiver) {
    track(target, key) // 收集依赖
    return Reflect.get(target, key, receiver)
  },
  set(target, key, value, receiver) {
    const result = Reflect.set(target, key, value, receiver)
    trigger(target, key) // 触发更新
    return result
  }
})

依赖收集与触发

Vue 3 内部维护了一个 WeakMap 结构来存储依赖关系:

  • targetMapWeakMap<target, Map<key, Dep>>
  • Dep:一个 Set,存储所有依赖该属性的副作用函数

当执行 effect(如组件的渲染函数、watchEffect 回调)时,会先将当前 effect 设置为全局的 activeEffect,然后在读取响应式属性时,track 函数会将 activeEffect 添加到对应属性的 Dep 中。当属性被修改时,trigger 函数会从 Dep 中取出所有 effect 并依次执行。

这种设计使得依赖收集是精确到属性级别的,避免了 Vue 2 中全量重新渲染的性能问题。

生命周期钩子与 watch

setup 中,生命周期钩子需要以 on 开头导入使用:

import { onMounted, onUnmounted, watch, watchEffect } from 'vue'

onMounted(() => {
  console.log('组件已挂载')
})

onUnmounted(() => {
  console.log('组件已卸载')
})

// watch:显式指定源
watch(count, (newVal, oldVal) => {
  console.log(`count 从 ${oldVal} 变为 ${newVal}`)
})

// watchEffect:自动收集依赖
watchEffect(() => {
  console.log(`当前 count 为 ${count.value}`)
})

watchEffect 会立即执行一次,并自动追踪函数体内用到的响应式依赖;而 watch 需要显式指定监听源,并且可以获取新旧值。

实战:封装一个 useFetch 组合函数

组合式 API 最大的优势在于逻辑复用。下面我们封装一个通用的数据请求组合函数:

import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)

  watchEffect(async () => {
    loading.value = true
    data.value = null
    error.value = null
    try {
      const res = await fetch(toValue(url))
      if (!res.ok) throw new Error(res.statusText)
      data.value = await res.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  })

  return { data, error, loading }
}

在组件中使用:

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

const id = ref(1)
const { data, error, loading } = useFetch(() => `/api/posts/${id.value}`)
</script>

<template>
  <div v-if="loading">加载中...</div>
  <div v-else-if="error">出错了:{{ error.message }}</div>
  <article v-else>{{ data.title }}</article>
</template>

这个例子展示了组合式 API 的威力:响应式数据、副作用逻辑和状态管理被封装在一个可复用的函数中,任何组件都可以直接使用。

总结

组合式 API 不仅是一种新的代码组织方式,更是 Vue 3 响应式系统能力的完全释放。从 setup 入口到 ref/reactive 的数据定义,从 Proxy 依赖收集到 effect 触发更新,每一层设计都体现了对开发体验和运行性能的深度考量。掌握组合式 API,意味着你能够写出更清晰、更可复用、类型更友好的 Vue 应用。

未经允许不得转载:任鹏个人博客 » Vue 3 组合式 API 深度解析:从 setup 到响应式原理的完整指南

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏