跳至内容

Vue 3 组合式 API

Vue 3 推荐使用**组合式 API(Composition API)**配合 <script setup> 语法糖来编写组件。相比 Vue 2 的选项式 API,组合式 API 将同一功能的代码集中在一起,更易维护、更好支持 TypeScript,也更方便逻辑复用。

与选项式 API 的对比

维度选项式 API(Vue 2)组合式 API(Vue 3)
代码组织data/methods/computed 分散按功能聚合,一处读懂
逻辑复用Mixin(命名冲突、来源不清晰)自定义 Hook(composable),清晰
TypeScript支持有限原生完整支持
推荐场景简单页面、迁移项目Vue 3 新项目首选

<script setup> 语法

<script setup>setup() 函数的编译时语法糖,顶层声明的变量和函数自动暴露给模板,无需 return

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

// 响应式数据
const count = ref(0)

function increment() {
  count.value++
}

// 生命周期钩子
onMounted(() => {
  console.log('组件已挂载,count =', count.value)
})
</script>

<template>
  <button @click="increment">点击次数{{ count }}</button>
</template>

响应式系统

ref — 基础响应式值

ref 用于包装任意类型的值(原始类型首选 ref)。在 JS 中通过 .value 访问,模板中自动解包无需 .value

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

const name = ref('Alice')
const count = ref(0)
const list = ref([1, 2, 3])

// 修改时必须用 .value
name.value = 'Bob'
list.value.push(4)
</script>

<template>
  <!-- 模板中自动解包不需要 .value -->
  <p>{{ name }}</p>
  <p>{{ count }}</p>
</template>

reactive — 对象响应式

reactive 用于包装对象/数组,返回深层响应式代理。直接通过属性访问,无需 .value

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

const state = reactive({
  name: 'Alice',
  age: 25,
  hobbies: ['读书', '编程']
})

// 直接修改属性
state.name = 'Bob'
state.hobbies.push('旅行')
</script>

不要解构 reactive 对象,解构后的变量会失去响应性。若需解构,使用 toRefs()

import { reactive, toRefs } from 'vue'
const state = reactive({ name: 'Alice', age: 25 })
const { name, age } = toRefs(state)  // name.value, age.value 依然响应

computed — 计算属性

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

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

// 只读计算属性
const fullName = computed(() => firstName.value + lastName.value)

// 可写计算属性
const fullNameWritable = computed({
  get: () => firstName.value + ' ' + lastName.value,
  set: (val) => {
    [firstName.value, lastName.value] = val.split(' ')
  }
})
</script>

<template>
  <p>全名{{ fullName }}</p>
</template>

watch — 侦听器

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

const count = ref(0)
const state = reactive({ name: 'Alice' })

// 侦听单个 ref
watch(count, (newVal, oldVal) => {
  console.log(`count: ${oldVal}${newVal}`)
})

// 侦听多个来源
watch([count, () => state.name], ([newCount, newName]) => {
  console.log(newCount, newName)
})

// 深度侦听对象(deep: true)
watch(state, (newState) => {
  console.log('state 变化了', newState)
}, { deep: true })

// watchEffect:自动追踪依赖,立即执行
watchEffect(() => {
  // 访问到的响应式数据都会被追踪
  console.log(`count 是 ${count.value},name 是 ${state.name}`)
})
</script>

生命周期钩子

组合式 API 中的钩子需从 vue 导入,对应关系:

选项式 API组合式 API(<script setup>
beforeCreate / createdsetup() 本身(无对应钩子)
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
<script setup>
import { onMounted, onUpdated, onUnmounted } from 'vue'

onMounted(() => {
  console.log('DOM 已挂载,可以操作 DOM 或发起请求')
})

onUnmounted(() => {
  // 清理定时器、取消订阅等
})
</script>

组件通信

defineProps — 父传子

<!-- 子组件 ChildComp.vue -->
<script setup>
// 方式一:运行时声明
const props = defineProps({
  title: String,
  count: {
    type: Number,
    default: 0
  },
  required: {
    type: Boolean,
    required: true
  }
})

// 方式二:TypeScript 类型声明(推荐)
// const props = defineProps<{ title: string; count?: number }>()
</script>

<template>
  <h2>{{ props.title }}{{ props.count }}</h2>
</template>
<!-- 父组件 -->
<template>
  <ChildComp title="标题" :count="42" :required="true" />
</template>

defineEmits — 子传父

<!-- 子组件 -->
<script setup>
const emit = defineEmits(['update', 'delete'])

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

<template>
  <button @click="handleUpdate">更新</button>
  <button @click="emit('delete', 1)">删除</button>
</template>
<!-- 父组件 -->
<template>
  <ChildComp @update="onUpdate" @delete="onDelete" />
</template>

<script setup>
function onUpdate(payload) { console.log(payload) }
function onDelete(id) { console.log('删除', id) }
</script>

v-model 双向绑定

Vue 3 中 v-model 默认使用 modelValue prop 和 update:modelValue 事件,支持多个 v-model:

<!-- 子组件 MyInput.vue -->
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="modelValue"
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>
<!-- 父组件 -->
<template>
  <MyInput v-model="text" />
  <!-- 等价于<MyInput :modelValue="text" @update:modelValue="text = $event" /> -->
</template>

provide / inject — 跨层传值

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

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

const theme = inject('theme')
const updateTheme = inject('updateTheme')
</script>

<template>
  <div :class="theme">
    <button @click="updateTheme('light')">切换亮色</button>
  </div>
</template>

可组合函数(Composable)

将可复用的有状态逻辑提取为函数,命名以 use 开头:

// composables/useFetch.js
import { ref } from 'vue'

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

  fetch(url)
    .then(res => res.json())
    .then(json => { data.value = json })
    .catch(err => { error.value = err })
    .finally(() => { loading.value = false })

  return { data, error, loading }
}
<!-- 使用 composable -->
<script setup>
import { useFetch } from '@/composables/useFetch'

const { data, error, loading } = useFetch('/api/users')
</script>

<template>
  <div v-if="loading">加载中</div>
  <div v-else-if="error">出错了</div>
  <ul v-else>
    <li v-for="user in data" :key="user.id">{{ user.name }}</li>
  </ul>
</template>

常用模板指令速查

指令说明示例
v-bind / :动态绑定属性:href="url"
v-on / @绑定事件@click="fn"
v-model双向绑定v-model="text"
v-if / v-else-if / v-else条件渲染(销毁/重建)v-if="show"
v-show条件显示(切换 display)v-show="visible"
v-for列表渲染v-for="(item, i) in list" :key="i"
v-once只渲染一次v-once
v-pre跳过编译(展示原始 Mustache)v-pre
最后更新于