组合式API #

一、组合式API概述 #

1.1 什么是组合式API #

组合式API(Composition API)是Vue 3引入的一组新API,允许使用函数组织组件逻辑。

1.2 与选项式API对比 #

vue
<!-- 选项式API -->
<script>
export default {
  data() {
    return {
      count: 0,
      name: '张三'
    }
  },
  computed: {
    doubled() {
      return this.count * 2
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  mounted() {
    console.log('组件已挂载')
  }
}
</script>

<!-- 组合式API -->
<script setup>
import { ref, computed, onMounted } from 'vue'

const count = ref(0)
const name = ref('张三')

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

function increment() {
  count.value++
}

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

1.3 组合式API的优势 #

  • 更好的逻辑复用:通过组合式函数实现
  • 更灵活的代码组织:按功能而非选项分组
  • 更好的类型推断:TypeScript支持更完善
  • 更小的打包体积:Tree-shaking更有效

二、setup函数 #

2.1 基本使用 #

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

export default {
  setup() {
    const count = ref(0)
    
    function increment() {
      count.value++
    }
    
    return {
      count,
      increment
    }
  }
}
</script>

2.2 setup语法糖 #

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

const count = ref(0)

function increment() {
  count.value++
}
</script>

2.3 访问Props #

vue
<script setup>
const props = defineProps({
  title: String,
  count: {
    type: Number,
    default: 0
  }
})

console.log(props.title)
</script>

2.4 定义Emits #

vue
<script setup>
const emit = defineEmits(['update', 'delete'])

function handleClick() {
  emit('update', { id: 1 })
}
</script>

2.5 setup参数 #

vue
<script>
export default {
  setup(props, context) {
    // props - 响应式的props对象
    console.log(props.title)
    
    // context - 上下文对象
    // context.attrs - 非props属性
    // context.slots - 插槽
    // context.emit - 触发事件
    // context.expose - 暴露给模板引用
    
    return {}
  }
}
</script>

三、生命周期钩子 #

3.1 钩子函数对照表 #

选项式API 组合式API
beforeCreate setup()
created setup()
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

3.2 使用示例 #

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

onBeforeMount(() => {
  console.log('组件挂载前')
})

onMounted(() => {
  console.log('组件已挂载')
  // DOM操作、事件监听、定时器等
})

onBeforeUpdate(() => {
  console.log('组件更新前')
})

onUpdated(() => {
  console.log('组件已更新')
})

onBeforeUnmount(() => {
  console.log('组件卸载前')
  // 清理工作
})

onUnmounted(() => {
  console.log('组件已卸载')
  // 清理定时器、事件监听等
})
</script>

3.3 清理副作用 #

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

const timer = ref(null)

onMounted(() => {
  timer.value = setInterval(() => {
    console.log('定时器执行')
  }, 1000)
  
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  // 清理定时器
  clearInterval(timer.value)
  
  // 移除事件监听
  window.removeEventListener('resize', handleResize)
})

function handleResize() {
  console.log('窗口大小变化')
}
</script>

3.4 使用watchEffect自动清理 #

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

const userId = ref(1)

watchEffect((onCleanup) => {
  const controller = new AbortController()
  
  fetch(`/api/users/${userId.value}`, {
    signal: controller.signal
  })
    .then(res => res.json())
    .then(data => {
      // 处理数据
    })
  
  // 自动清理:下次执行前或组件卸载时调用
  onCleanup(() => {
    controller.abort()
  })
})
</script>

四、依赖注入 #

4.1 provide和inject #

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

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

provide('theme', theme)
provide('user', user)
</script>

<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'

const theme = inject('theme')
const user = inject('user')

console.log(theme.value)  // 'dark'
</script>

4.2 提供默认值 #

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

// 提供默认值
const theme = inject('theme', 'light')

// 使用工厂函数
const config = inject('config', () => ({
  apiUrl: '/api',
  timeout: 5000
}))
</script>

4.3 响应式注入 #

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

const state = ref({
  count: 0,
  user: null
})

// 提供只读状态
provide('state', readonly(state))

// 提供修改方法
provide('actions', {
  increment: () => state.value.count++,
  setUser: (user) => state.value.user = user
})
</script>

<!-- 后代组件 -->
<script setup>
import { inject } from 'vue'

const state = inject('state')
const actions = inject('actions')

function handleClick() {
  actions.increment()
}
</script>

4.4 使用Symbol作为key #

javascript
// keys.js
export const ThemeKey = Symbol('theme')
export const UserKey = Symbol('user')
export const StateKey = Symbol('state')
vue
<script setup>
import { provide, inject } from 'vue'
import { ThemeKey, UserKey } from './keys'

// 提供
provide(ThemeKey, 'dark')

// 注入
const theme = inject(ThemeKey)
</script>

五、模板引用 #

5.1 基本使用 #

vue
<template>
  <input ref="inputRef">
  <button @click="focusInput">聚焦</button>
</template>

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

const inputRef = ref(null)

function focusInput() {
  inputRef.value.focus()
}

onMounted(() => {
  inputRef.value.focus()
})
</script>

5.2 组件引用 #

vue
<template>
  <Child ref="childRef" />
  <button @click="callChildMethod">调用子组件方法</button>
</template>

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

const childRef = ref(null)

function callChildMethod() {
  childRef.value.someMethod()
}
</script>
vue
<!-- Child.vue -->
<script setup>
import { ref } from 'vue'

const count = ref(0)

function increment() {
  count.value++
}

function someMethod() {
  console.log('子组件方法')
}

// 暴露给父组件
defineExpose({
  count,
  increment,
  someMethod
})
</script>

5.3 v-for中的引用 #

vue
<template>
  <div v-for="item in items" :key="item.id" :ref="setItemRef">
    {{ item.name }}
  </div>
</template>

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

const items = ref([
  { id: 1, name: '项目1' },
  { id: 2, name: '项目2' }
])

const itemRefs = ref([])

function setItemRef(el) {
  if (el) {
    itemRefs.value.push(el)
  }
}

onMounted(() => {
  console.log(itemRefs.value)  // 所有DOM元素
})
</script>

六、组合式函数 #

6.1 创建组合式函数 #

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

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const doubled = computed(() => count.value * 2)
  
  function increment() {
    count.value++
  }
  
  function decrement() {
    count.value--
  }
  
  function reset() {
    count.value = initialValue
  }
  
  return {
    count,
    doubled,
    increment,
    decrement,
    reset
  }
}

6.2 使用组合式函数 #

vue
<template>
  <div>
    <p>计数: {{ count }}</p>
    <p>双倍: {{ doubled }}</p>
    <button @click="increment">+1</button>
    <button @click="decrement">-1</button>
    <button @click="reset">重置</button>
  </div>
</template>

<script setup>
import { useCounter } from './useCounter'

const { count, doubled, increment, decrement, reset } = useCounter(10)
</script>

6.3 带参数的组合式函数 #

javascript
// useFetch.js
import { ref, watchEffect, toValue } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)
  const loading = ref(false)
  
  async function fetchData() {
    loading.value = true
    error.value = null
    
    try {
      const response = await fetch(toValue(url))
      data.value = await response.json()
    } catch (e) {
      error.value = e
    } finally {
      loading.value = false
    }
  }
  
  watchEffect(() => {
    fetchData()
  })
  
  return {
    data,
    error,
    loading,
    refetch: fetchData
  }
}
vue
<script setup>
import { ref } from 'vue'
import { useFetch } from './useFetch'

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

6.4 返回响应式对象 #

javascript
// useUser.js
import { reactive, toRefs } from 'vue'

export function useUser() {
  const state = reactive({
    user: null,
    loading: false,
    error: null
  })
  
  async function fetchUser(id) {
    state.loading = true
    state.error = null
    
    try {
      const response = await fetch(`/api/users/${id}`)
      state.user = await response.json()
    } catch (e) {
      state.error = e
    } finally {
      state.loading = false
    }
  }
  
  return {
    ...toRefs(state),
    fetchUser
  }
}
vue
<script setup>
import { useUser } from './useUser'

const { user, loading, error, fetchUser } = useUser()

fetchUser(1)
</script>

七、最佳实践 #

7.1 命名规范 #

javascript
// 组合式函数以use开头
export function useCounter() {}
export function useFetch() {}
export function useLocalStorage() {}

// 响应式变量使用描述性名称
const isLoading = ref(false)
const hasError = ref(false)
const userList = ref([])

7.2 参数处理 #

javascript
import { toValue, toRef, unref } from 'vue'

export function useExample(source) {
  // toValue - 解包ref和getter
  const value = toValue(source)
  
  // 支持ref、reactive、getter作为参数
  const data = computed(() => {
    return fetchData(toValue(source))
  })
  
  return { data }
}

// 使用方式灵活
useFetch('/api/data')           // 字符串
useFetch(ref('/api/data'))      // ref
useFetch(() => `/api/${id}`)    // getter

7.3 返回值规范 #

javascript
// 返回ref或reactive对象
export function useCounter() {
  const count = ref(0)
  return { count }  // 返回ref
}

// 使用toRefs保持响应性
export function useUser() {
  const state = reactive({
    user: null,
    loading: false
  })
  return toRefs(state)  // 解构后仍保持响应性
}

// 返回只读对象
export function useStore() {
  const state = reactive({ /* ... */ })
  return {
    state: readonly(state),
    actions: { /* ... */ }
  }
}

八、总结 #

组合式API核心 #

API 用途
setup 组合式API入口
ref 创建响应式引用
reactive 创建响应式对象
computed 创建计算属性
watch 侦听数据变化
watchEffect 自动追踪依赖
provide/inject 依赖注入
onMounted 生命周期钩子

最佳实践:

  • 优先使用<script setup>语法
  • 组合式函数以use开头命名
  • 使用toRefs返回响应式对象
  • 合理使用provide/inject
  • onUnmounted中清理副作用
最后更新:2026-03-26