Vue 3 组合式 API 深度解析:Composition API 实战

Vue 3 组合式 API 深度解析:Composition API 实战

admin
2026-06-25 / 0 评论 / 5 阅读
组合式 API(Composition API)是 Vue 3 最重要的特性,解决了 Options API 在复杂组件中逻辑分散的问题。本文通过实战案例深入讲解 setup、响应式 API、生命周期钩子和自定义组合函数。

一、为什么需要组合式 API

Options API 的痛点

// Options API:同一功能的代码被拆散到不同选项中
export default {
  data() {
    return {
      // 搜索功能的数据
      searchQuery: '',
      searchResults: [],
      // 用户功能的数据
      user: null,
      userPermissions: [],
    }
  },
  computed: {
    // 搜索功能的计算属性
    filteredResults() { /* ... */ },
    // 用户功能的计算属性
    isAdmin() { /* ... */ },
  },
  methods: {
    // 搜索功能的方法
    handleSearch() { /* ... */ },
    // 用户功能的方法
    loadUser() { /* ... */ },
  },
  mounted() {
    // 搜索和用户的初始化逻辑混在一起
    this.handleSearch()
    this.loadUser()
  }
}

组合式 API 的优势

<script setup>
// 搜索功能的所有代码聚合在一起
const { searchQuery, searchResults, handleSearch } = useSearch()

// 用户功能的所有代码聚合在一起
const { user, isAdmin, loadUser } = useUser()
</script>
优势说明
逻辑复用自定义组合函数替代 mixin,无命名冲突
代码组织相关逻辑聚合,而非按选项类型分散
类型推导更好的 TypeScript 支持
代码压缩setup 语法糖减少样板代码
生产性能编译优化(静态提升、patch flag)

二、setup 与 script setup

基本用法

<script setup>
import { ref, computed, onMounted } from 'vue'

// 顶层变量自动暴露给模板
const count = ref(0)
const message = 'Hello'

const double = computed(() => count.value * 2)

function increment() {
  count.value++
}

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

<template>
  <button @click="increment">{{ count }} × 2 = {{ double }}</button>
</template>

defineProps 与 defineEmits

<script setup>
// 编译宏,无需导入
const props = defineProps({
  title: String,
  items: {
    type: Array,
    default: () => []
  },
  disabled: Boolean
})

// TypeScript 方式
// const props = defineProps<{
//   title: string
//   items: Item[]
//   disabled?: boolean
// }>()

const emit = defineEmits(['update', 'delete'])

// 带验证的 emit
// const emit = defineEmits<{
//   (e: 'update', value: number): void
//   (e: 'delete', id: string): void
// }>()

function handleUpdate() {
  emit('update', props.title)
}
</script>

defineExpose 与 defineModel

<script setup>
import { ref } from 'vue'

const count = ref(0)

// 暴露给父组件的方法
defineExpose({
  reset: () => { count.value = 0 },
  getValue: () => count.value
})

// Vue 3.4+ defineModel 双向绑定
const modelValue = defineModel({ default: '' })
const visible = defineModel('visible', { type: Boolean, default: false })
</script>

三、响应式 API

ref 与 reactive

import { ref, reactive, shallowRef, shallowReactive } from 'vue'

// ref — 适合基本类型和需要整体替换的对象
const count = ref(0)
const user = ref({ name: '张三', age: 25 })

console.log(count.value)        // 访问需 .value
user.value.name = '李四'        // 修改属性
user.value = { name: '王五' }   // 整体替换

// reactive — 适合不替换整体的复合对象
const state = reactive({
  items: [],
  loading: false,
  filters: { category: null, price: null }
})

state.loading = true            // 直接访问,无需 .value
state.items.push({ id: 1 })

// ⚠️ reactive 的陷阱
let { items } = state           // ❌ 解构丢失响应式
items = toRef(state, 'items')   // ✅ 用 toRef 保持响应式

ref vs reactive 选择原则

场景推荐
基本类型ref
需要整体替换ref
表单对象reactive
组合函数返回值ref(保持统一)
大型只读配置shallowRef / markRaw

readonly 与 shallowRef

import { readonly, shallowRef, triggerRef, markRaw } from 'vue'

// readonly — 防止修改(如传递给子组件的 props)
const original = reactive({ count: 0 })
const copy = readonly(original)
// copy.count++  // ⚠️ 警告:无法修改

// shallowRef — 只追踪 .value 本身,适合大列表
const list = shallowRef([])

// 替换整个数组触发更新
list.value = [...list.value, newItem]

// 修改内部属性后手动触发
list.value.push(anotherItem)
triggerRef(list)

// markRaw — 排除响应式转换(如第三方类实例)
const chart = markRaw(new ECharts())

computed 与 watch

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

const firstName = ref('三')
const lastName = ref('张')

// computed — 有缓存
const fullName = computed(() => `${lastName.value}${firstName.value}`)

// 可写 computed
const fullNameWritable = computed({
  get: () => `${lastName.value}${firstName.value}`,
  set: (val) => {
    lastName.value = val[0]
    firstName.value = val.slice(1)
  }
})

// watch — 明确监听源,可获取旧值
watch(fullName, (newVal, oldVal) => {
  console.log(`${oldVal} → ${newVal}`)
})

// watch 对象属性
watch(
  () => state.filters,
  (newFilters) => { fetchList(newFilters) },
  { deep: true, immediate: true }
)

// watchEffect — 立即执行,自动追踪依赖
watchEffect(() => {
  console.log(`${firstName.value} ${lastName.value}`)
})

// watchEffect 清理副作用
watchEffect((onCleanup) => {
  const timer = setTimeout(() => {
    console.log(firstName.value)
  }, 500)
  
  onCleanup(() => clearTimeout(timer))  // 依赖变化时清理
})

toRefs 与 toValue

import { toRefs, toRef, toValue, reactive } from 'vue'

const state = reactive({ x: 1, y: 2 })

// toRefs — 用于解构 reactive 对象时保持响应式
const { x, y } = toRefs(state)

// toRef — 单个属性
const yRef = toRef(state, 'y')

// toValue — 统一处理 ref / getter / 普通值(Vue 3.3+)
function resolveValue(maybeRefOrGetter) {
  return toValue(maybeRefOrGetter)
}

resolveValue(refValue)    // ref → .value
resolveValue(() => 123)   // getter → 调用结果
resolveValue(123)         // 普通值 → 原样返回

四、生命周期钩子

<script setup>
import {
  onBeforeMount, onMounted,
  onBeforeUpdate, onUpdated,
  onBeforeUnmount, onUnmounted,
  onErrorCaptured, onActivated, onDeactivated
} from 'vue'

onBeforeMount(() => console.log('挂载前'))
onMounted(() => console.log('DOM 已就绪'))
onBeforeUpdate(() => console.log('更新前'))
onUpdated(() => console.log('更新后'))
onBeforeUnmount(() => console.log('卸载前'))
onUnmounted(() => console.log('已卸载'))

// 捕获子组件错误
onErrorCaptured((err, instance, info) => {
  console.error('子组件错误:', err)
  return false  // 阻止继续传播
})

// KeepAlive 相关
onActivated(() => console.log('组件激活'))
onDeactivated(() => console.log('组件失活'))
</script>

五、自定义组合函数

useFetch — 数据请求

// composables/useFetch.js
import { ref, shallowRef, triggerRef, toValue, watchEffect } from 'vue'

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

  async function doFetch() {
    data.value = null
    error.value = null
    loading.value = true

    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
    }
  }

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

  return { data, error, loading, retry: doFetch }
}
<script setup>
import { ref } from 'vue'
import { useFetch } from '@/composables/useFetch'

const userId = ref(1)
const { data: user, error, loading } = useFetch(
  () => `/api/users/${userId.value}`  // getter 形式
)
</script>

<template>
  <div v-if="loading">加载中...</div>
  <div v-else-if="error">错误: {{ error.message }}</div>
  <div v-else-if="user">{{ user.name }}</div>
</template>

useLocalStorage — 本地存储

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

export function useLocalStorage(key, defaultValue) {
  const value = ref(
    JSON.parse(localStorage.getItem(key)) ?? defaultValue
  )

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

  function remove() {
    localStorage.removeItem(key)
    value.value = defaultValue
  }

  return { value, remove }
}

useMouse — 鼠标追踪

// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function update(e) {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}

useDebounceFn — 防抖

// composables/useDebounceFn.js
import { customRef } from 'vue'

export function debouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

六、provide 与 inject

<!-- 祖先组件 -->
<script setup>
import { provide, ref, readonly } from 'vue'

const theme = ref('dark')
const user = ref({ name: '张三' })

// 提供只读数据
provide('theme', readonly(theme))
provide('user', readonly(user))

// 提供修改方法
provide('updateTheme', (val) => { theme.value = val })
</script>
<!-- 后代组件(任意深度) -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme', 'light')           // 带默认值
const user = inject('user')
const updateTheme = inject('updateTheme')

updateTheme('light')
</script>

七、小结

组合式 API 是 Vue 3 的核心范式:ref/reactive 管理状态,computed/watch 处理派生与副作用,自定义组合函数实现逻辑复用。掌握这些 API 后,你会发现复杂组件的开发变得清晰而高效。


本文由 inspirecl.asia 原创,转载请注明出处。
0

评论 (0)

取消
0:00