Vue3 Composition API实战:setup语法糖与hooks设计模式

发布于2026-07-24 12:06 阅读16次 Vue3的Composition API彻底改变了组件逻辑的组织方式,本文结合实际项目经验讲解setup语法糖、响应式原理、自定义hooks的设计模式与复用技巧,帮你写出更清晰易维护的Vue代码。
# Vue3 Composition API实战:setup语法糖与hooks设计模式
## 引言
Vue3最大的变化之一就是引入了Composition API。相比Options API,Composition API允许我们将同一个逻辑关注点的代码集中在一起,而不是分散在data、methods、computed等各个选项中。这不仅让大型组件更易维护,也为逻辑复用提供了更优雅的解决方案。本文结合实际项目经验,深入讲解Composition API的使用技巧与自定义hooks的设计模式。
## 一、setup函数基础与响应式原理
### 1.1 setup的执行时机
setup是Vue3组件生命周期中最先执行的选项,在beforeCreate之前调用。此时组件实例还未创建,因此无法访问this,但可以正常使用 reactivity APIs。
```javascript
export default {
setup() {
console.log('setup执行时组件实例还未创建');
// this 为 undefined
return {
// 这里返回的内容会在模板中可用
};
}
};
```
### 1.2 响应式数据:ref与reactive
Vue3提供了两个创建响应式数据的方式——ref和reactive,各有适用场景。
```javascript
import { ref, reactive } from 'vue';
// ref:适合基本类型,也可包装对象
const count = ref(0);
console.log(count.value); // 访问值需要.value
// reactive:适合复杂类型(对象/数组),直接代理
const state = reactive({
name: '张三',
items: []
});
console.log(state.name); // 直接访问
```
**避坑提示**:不要对reactive包装过的对象进行解构,解构后变量会失去响应式。使用toRefs可以将reactive对象转为ref的集合。
## 二、setup语法糖:让代码更简洁
### 2.1 script setup
Vue3.2引入的script setup语法糖是官方推荐的写法,极大简化了代码量:
```vue
<script setup>
import { ref, computed } from 'vue';
const msg = ref('你好');
const reversed = computed(() => msg.value.split('').reverse().join(''));
</script>
<template>
<div>{{ msg }} → {{ reversed }}</div>
</template>
```
使用script setup后,组件中定义的变量和函数**自动暴露给模板**,无需手动return。
### 2.2 defineProps与defineEmits
在script setup中,props和emits有更简洁的声明方式:
```vue
<script setup>
// 带默认值的方式
const props = defineProps({
title: { type: String, required: true },
count: { type: Number, default: 0 }
});
const emit = defineEmits(['update', 'delete']);
// 使用
emit('update', '新标题');
</script>
```
## 三、自定义Hooks:Composition API的精髓
### 3.1 什么是Hooks
Hooks本质是一个返回响应式数据的函数。在Vue2时代,mixins是逻辑复用的主要手段,但mixins存在命名冲突和数据来源不清晰的缺点。Hooks完全避免了这些问题。
```javascript
// useWindowSize.js
export function useWindowSize() {
const width = ref(window.innerWidth);
const height = ref(window.innerHeight);
const handleResize = () => {
width.value = window.innerWidth;
height.value = window.innerHeight;
};
onMounted(() => window.addEventListener('resize', handleResize));
onUnmounted(() => window.removeEventListener('resize', handleResize));
return { width, height };
}
```
### 3.2 在组件中使用
```vue
<script setup>
import { useWindowSize } from '@/hooks/useWindowSize';
const { width, height } = useWindowSize();
</script>
<template>
<div>窗口尺寸: {{ width }} x {{ height }}</div>
</template>
```
每个组件调用hooks都会创建独立的响应式状态,组件之间互不影响,这是相比mixins的巨大优势。
## 四、实战:表单处理Hook
### 4.1 useForm
结合实际项目,以下是一个完整的表单处理hooks:
```javascript
import { reactive, ref } from 'vue';
export function useForm(initialValues, rules = {}) {
const form = reactive({ ...initialValues });
const errors = reactive({});
const isSubmitting = ref(false);
const validate = () => {
let valid = true;
Object.keys(rules).forEach(field => {
const value = form[field];
const rule = rules[field];
if (rule.required && !value) {
errors[field] = rule.message || '此字段必填';
valid = false;
} else if (rule.pattern && !rule.pattern.test(value)) {
errors[field] = rule.message || '格式不正确';
valid = false;
} else {
errors[field] = '';
}
});
return valid;
};
const resetForm = () => {
Object.keys(initialValues).forEach(key => {
form[key] = initialValues[key];
errors[key] = '';
});
};
return { form, errors, isSubmitting, validate, resetForm };
}
```
### 4.2 使用示例
```vue
<script setup>
import { useForm } from '@/hooks/useForm';
const { form, errors, validate, resetForm } = useForm(
{ username: '', password: '' },
{
username: { required: true, message: '用户名不能为空' },
password: { required: true, message: '密码不能为空', pattern: /^[a-zA-Z0-9]{6,}$/ }
}
);
const handleSubmit = async () => {
if (!validate()) return;
// 提交表单逻辑...
};
</script>
```
## 五、异步数据获取的最佳实践
### 5.1 useAsync
在真实项目中,异步数据获取是最常见的场景。以下是一个封装了加载状态、错误处理的hooks:
```javascript
import { ref } from 'vue';
export function useAsync(asyncFn, immediate = true) {
const data = ref(null);
const loading = ref(false);
const error = ref(null);
const execute = async (...args) => {
loading.value = true;
error.value = null;
try {
data.value = await asyncFn(...args);
return data.value;
} catch (err) {
error.value = err;
throw err;
} finally {
loading.value = false;
}
};
if (immediate) {
execute();
}
return { data, loading, error, execute };
}
```
### 5.2 使用示例
```vue
<script setup>
import useAsync from '@/hooks/useAsync';
const { data: userList, loading, error, execute: reload } = useAsync(
() => fetch('/api/users').then(r => r.json()),
false
);
onMounted(() => reload());
</script>
```
## 六、注意事项与常见错误
### 6.1 避免在setup外访问ref的值
响应式数据必须在setup函数内部或hook内部创建,一旦在setup外访问.value,就会丢失响应式追踪。
### 6.2 合理拆分Hooks粒度
不要把过多无关逻辑塞进一个hook,保持每个hook职责单一。比如不要写一个useUserAndPosts这样的大杂烩,而是拆成useUser和usePosts。
### 6.3 依赖注入配合使用
provide/inject在 Composition API中同样适用,适合在多层嵌套组件间传递hooks实例,实现跨层级逻辑共享。
## 总结
Vue3 Composition API的核心优势在于:
1. **更好的逻辑组织**:同一逻辑关注点的代码集中在一起
2. **更清晰的逻辑复用**:自定义hooks没有命名冲突,状态来源一目了然
3. **更好的TypeScript支持**:类型推断更准确
4. **更小的打包体积**:未使用的API不会被打包
掌握这些技巧,你离Vue高手又近了一步。