Skip to content

Composition API ​

Composition API 是 Vue 3 最重要的新增特性之一,核心动机是逻辑复用和更好的代码组织。

为什么要 Composition API? ​

Options API 的问题 ​

js
// Vue 2 Options API — 一个功能的代码散落在各处
export default {
  data() { return { keyword: '', results: [] } },
  watch: { keyword: 'search' },
  methods: { search() { /*...*/ } },
  mounted() { /* 初始化 */ }
}

// 当功能变多后:
export default {
  data() {
    return {
      keyword: '',       // ← 搜索
      results: [],       // ← 搜索
      isLoading: false,  // ← 搜索
      user: null,        // ← 用户
      permissions: [],   // ← 权限
    }
  },
  methods: {
    search() {},        // ← 搜索
    fetchUser() {},     // ← 用户
    checkAuth() {},     // ← 权限
  }
  // 三个功能的代码交织在一起,难以维护
}

Composition API 的解决 ​

js
// 按功能组织,逻辑内聚
import { useSearch, useUser, useAuth } from './composables'

export default {
  setup() {
    const { keyword, results, search } = useSearch()
    const { user, fetchUser } = useUser()
    const { permissions, checkAuth } = useAuth()
    return { keyword, results, search, user, permissions }
  }
}

setup 函数 ​

html
<template>
  <div>{{ count }}</div>
  <button @click="increment">+1</button>
</template>

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

export default {
  setup() {
    const count = ref(0)
    const increment = () => count.value++

    onMounted(() => {
      console.log('mounted!')
    })

    return { count, increment }  // 暴露给模板
  }
}
</script>

<script setup> 语法糖(推荐) ​

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

const count = ref(0)
const increment = () => count.value++

onMounted(() => {
  console.log('mounted!')
})
// 无需 return,自动暴露
</script>

reactive vs ref ​

reactive()ref()
参数类型仅对象任意类型
访问方式直接 .property需要 .value
模板中直接使用自动解包 .value
解构❌ 丢失响应式✅ 配合 toRefs 不丢失
整体替换会丢失响应式✅ count.value = newVal
js
// reactive 解构问题
const state = reactive({ a: 1, b: 2 })
const { a, b } = state         // ❌ a、b 变成普通值

// 解决方案:toRefs
const { a, b } = toRefs(state) // ✅ a、b 是 ref

// 或者直接用 ref
const a = ref(1)               // ✅ 天生可以独立传递

computed ​

js
import { computed, ref } from 'vue'

const count = ref(0)

// 只读
const doubled = computed(() => count.value * 2)

// 可写
const doubledWritable = computed({
  get: () => count.value * 2,
  set: (val) => { count.value = val / 2 }
})

watch vs watchEffect ​

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

const count = ref(0)
const name = ref('vue')

// watch:明确指定监听源,可获取新旧值
watch(count, (newVal, oldVal) => {
  console.log(`${oldVal} → ${newVal}`)
})
watch([count, name], ([newCount, newName]) => {})

// watchEffect:自动追踪依赖,立即执行
watchEffect(() => {
  console.log(count.value, name.value)  // count 或 name 变了都执行
})

// 深层监听
watch(() => reactiveObj, (newVal) => {}, { deep: true })
watchwatchEffect
监听源显式指定自动追踪
旧值✅ newVal/oldVal❌
立即执行immediate: true✅ 默认立即
停止返回 stop 函数返回 stop 函数

生命周期钩子 ​

js
// Composition API                  // Options API
import { onBeforeMount }    from 'vue'  // beforeMount
import { onMounted }        from 'vue'  // mounted
import { onBeforeUpdate }   from 'vue'  // beforeUpdate
import { onUpdated }        from 'vue'  // updated
import { onBeforeUnmount }  from 'vue'  // beforeDestroy
import { onUnmounted }      from 'vue'  // destroyed
import { onActivated }      from 'vue'  // activated
import { onDeactivated }    from 'vue'  // deactivated

没有 beforeCreate 和 created,因为 setup() 本身就等价于这两个钩子的时机。

逻辑复用 — Composables ​

这是 Composition API 最大的优势。把可复用的逻辑抽离为"组合函数":

js
// 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(() => document.addEventListener('mousemove', update))
  onUnmounted(() => document.removeEventListener('mousemove', update))

  return { x, y }
}

// 使用
const { x, y } = useMouse()

vs Vue 2 Mixins ​

MixinsComposables
来源不清晰import 显式引入
命名冲突解构重命名
隐式依赖显式参数传入
不支持 TS 类型推导✅ 完美 TS 支持

依赖注入 — provide/inject ​

js
// 祖先组件
import { provide, ref } from 'vue'
const theme = ref('dark')
provide('theme', theme)

// 后代组件
import { inject } from 'vue'
const theme = inject('theme', 'light')  // 默认值 'light'

与 Vue 2 的区别:支持响应式、TS 类型、readonly 单向约束。

基于 Vue 2.7 & Vue 3.4 源码