首页 关于 文章 作品集 工具 联系
返回文章列表

Vue3 组合式 API 与响应式原理深度解析

Vue3 的组合式 API(Composition API)不仅是新的代码组织方式,更是 Vue 对现代前端开发范式的回应。它解决了 Options API 在大型组件中代码组织混乱、逻辑复用困难的问题。本文深入讲解组合式 API 的核心概念和 Vue3 响应式系统的底层原理。

一、为什么需要组合式 API?

Options API 按类型组织代码(data、methods、computed、watch),在小型组件中清晰直观。但随着组件变大,同一功能的代码会被拆散到不同选项中,修改一个功能要在多个区域之间来回跳转。

// ❌ Options API:同一功能的代码散落各处
export default {
  data() {
    return { searchQuery: '', results: [], page: 1 }
  },
  computed: {
    filteredResults() { /* 搜索过滤 */ },
    hasMore() { /* 分页判断 */ }
  },
  methods: {
    search() { /* 搜索请求 */ },
    loadMore() { /* 加载更多 */ }
  },
  mounted() { this.search() }
}

// ✅ Composition API:功能内聚,逻辑集中
import { ref, computed, onMounted } from 'vue'

export default {
  setup() {
    // ===== 搜索功能(所有相关代码集中在一起)=====
    const searchQuery = ref('')
    const results = ref([])
    const page = ref(1)

    const filteredResults = computed(() =>
      results.value.filter(item => item.title.includes(searchQuery.value))
    )

    async function search() {
      const res = await fetch(`/api/search?q=${searchQuery.value}&page=${page.value}`)
      results.value = await res.json()
    }

    onMounted(search)
    return { searchQuery, filteredResults, search }
  }
}

二、核心 API 详解

2.1 ref 与 reactive

import { ref, reactive, toRefs } from 'vue'

// ref:任意类型的响应式引用(通过 .value 访问)
const count = ref(0)
count.value++
const user = ref({ name: '张兴中', age: 25 })


// reactive:对象类型的响应式(直接访问属性)
const state = reactive({
  name: '张兴中',
  posts: [],
  settings: { theme: 'dark' }
})
state.name = '新名字'  // 无需 .value

// ⚠️ 解构 reactive 会丢失响应性,用 toRefs 解决
const { name, posts } = toRefs(state)  // 保持响应式
  // 解构后访问需要 .value

// 选型建议
// 基础类型 → ref;复杂嵌套对象 → reactive;两者均可时 → ref(更灵活,可整体替换)

2.2 computed 与 watch

import { ref, computed, watch, watchEffect } from 'vue'

const price = ref(100)
const quantity = ref(2)

// computed:带缓存的计算属性,依赖不变不重新计算
const total = computed(() => price.value * quantity.value)

// 可写的 computed
const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (val) => {
    [firstName.value, lastName.value] = val.split(' ')
  }
})

// watch:精确监听,可访问新旧值
watch(price, (newVal, oldVal) => {
  
})

// 监听多个源
watch([price, quantity], ([newP, newQ], [oldP, oldQ]) => {
  
})

// 深度监听对象
const user = ref({ address: { city: '盐城' } })
watch(user, (val) => , { deep: true, immediate: true })

// watchEffect:自动追踪所有依赖,立即执行
const stop = watchEffect(() => {
  
  // 自动追踪 price 和 quantity
})
// 停止监听
stop()

2.3 生命周期钩子

import { onMounted, onUpdated, onUnmounted, onBeforeUnmount } from 'vue'

onMounted(() => {
  // DOM 已挂载,可以操作 DOM、初始化第三方库
  const chart = echarts.init(document.getElementById('chart'))
  // ...
})

onBeforeUnmount(() => {
  // 组件即将卸载,清理副作用
  clearInterval(timer)
  chart.dispose()
  eventBus.off('message', handler)
})

// Options API → Composition API 对照
// beforeCreate / created → setup() 本身
// beforeMount          → onBeforeMount
// mounted              → onMounted
// beforeUpdate         → onBeforeUpdate
// updated              → onUpdated
// beforeUnmount        → onBeforeUnmount
// unmounted            → onUnmounted

三、自定义 Hook(Composable)

组合式 API 最大的威力在于逻辑复用——把相关逻辑封装为独立函数,跨组件共享:

// composables/useFetch.ts
import { ref, unref, watchEffect } from 'vue'

export function useFetch<T>(url: string | Ref<string>) {
  const data = ref<T | null>(null)
  const error = ref<string | null>(null)
  const loading = ref(true)

  async function doFetch() {
    loading.value = true
    error.value = null
    try {
      const res = await fetch(unref(url))  // unref 兼容普通值和 ref
      if (!res.ok) throw new Error(`HTTP ${res.status}`)
      data.value = await res.json()
    } catch (err: any) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  // url 是响应式时自动重新请求
  watchEffect(() => doFetch())

  return { data, error, loading, refresh: doFetch }
}

// composables/useLocalStorage.ts
import { ref, watch } from 'vue'

export function useLocalStorage<T>(key: string, defaultValue: T) {
  const stored = localStorage.getItem(key)
  const data = ref<T>(stored ? JSON.parse(stored) : defaultValue)

  watch(data, (val) => {
    localStorage.setItem(key, JSON.stringify(val))
  }, { deep: true })

  return data
}

// 组件中使用
const { data: posts, loading, error } = useFetch('/api/posts')
const theme = useLocalStorage('theme', 'dark')

四、响应式原理:Proxy

Vue3 用 ES6 的 Proxy 重写响应式系统,取代了 Vue2 的 Object.defineProperty

// 简化版响应式系统
let activeEffect = null

function track(target, key) {
  if (!activeEffect) return
  // 建立 target.key → effects 的映射,收集依赖
}

function trigger(target, key) {
  // 找到 target.key 对应的所有 effects,重新执行
}

function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key)   // 读取时收集依赖
      const result = Reflect.get(target, key, receiver)
      // 深层代理:嵌套对象也变为响应式
      return typeof result === 'object' ? reactive(result) : result
    },
    set(target, key, value, receiver) {
      const oldValue = target[key]
      const result = Reflect.set(target, key, value, receiver)
      if (oldValue !== value) trigger(target, key)  // 变化时触发更新
      return result
    },
    deleteProperty(target, key) {
      const hadKey = key in target
      const result = Reflect.deleteProperty(target, key)
      if (hadKey) trigger(target, key)
      return result
    }
  })
}

// Proxy vs Object.defineProperty 优势
// ✅ 动态新增属性自动具备响应性(Vue2 需要 $set)
// ✅ 数组索引修改和 length 变化都能检测
// ✅ 支持 Map、Set、WeakMap 等数据结构
// ✅ 初始化性能更好(不需要遍历所有属性)

五、<script setup> 语法糖

Vue3.2 引入的 <script setup> 让组合式 API 更简洁:

<!-- ❌ 传统写法:需要手动 return -->
<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    return { count }  // 必须 return 才能在模板使用
  }
}
</script>

<!-- ✅ script setup:自动暴露,更简洁 -->
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useFetch } from './composables/useFetch'

const count = ref(0)                           // 自动在模板可用
const double = computed(() => count.value * 2)

const { data: posts, loading } = useFetch('/api/posts')

// defineProps / defineEmits 宏(无需 import)
const props = defineProps<{
  title: string
  initial?: number
}>()

const emit = defineEmits<{
  change: [value: number]
  submit: []
}>()
</script>

<template>
  <div>
    <h1>{{ props.title }}</h1>
    <p v-if="loading">加载中...</p>
    <p>{{ count }} × 2 = {{ double }}</p>
    <button @click="count++">+1</button>
  </div>
</template>

Vue3 的组合式 API 是现代 Vue 开发的正确姿势。理解 Proxy 响应式原理,掌握自定义 Hook 封装思路,用 <script setup> 简化代码——这三点就是 Vue3 进阶的核心。越用越会感受到它在大型项目中的优雅之处。