引言
Vue 3的Composition API是Vue框架最重大的更新之一,它提供了一种全新的组件逻辑组织方式。与传统的Options API相比,Composition API让我们能够更灵活地组织和复用代码逻辑。本文将深入探讨Vue 3 Composition API的8大核心特性,帮助你掌握这个构建可复用逻辑的终极方案。
setup函数基础
1. setup函数的基本使用
setup函数是Composition API的入口点,它在组件创建之前执行。
javascript
import { ref, reactive } from 'vue';
export default {
setup() {
// 定义响应式数据
const count = ref(0);
const user = reactive({
name: 'Vue 3',
version: '3.0'
});
// 定义方法
const increment = () => {
count.value++;
};
// 返回给模板使用
return {
count,
user,
increment
};
}
};
2. setup函数的参数
setup函数接收两个参数:props和context。
javascript
export default {
props: {
title: String,
initialCount: {
type: Number,
default: 0
}
},
setup(props, context) {
// props是响应式的,不能解构
console.log(props.title);
// context包含attrs、slots、emit等
const { attrs, slots, emit } = context;
// 触发事件
const handleClick = () => {
emit('update', props.initialCount + 1);
};
return { handleClick };
}
};
响应式API详解
3. ref与reactive的选择
ref和reactive是创建响应式数据的两种方式,各有适用场景。
javascript
import { ref, reactive, toRefs } from 'vue';
// ref - 适合基本类型和单一对象
const count = ref(0);
const message = ref('Hello');
// 访问ref的值需要.value
console.log(count.value);
count.value++;
// reactive - 适合复杂对象
const state = reactive({
count: 0,
user: {
name: 'Vue',
age: 3
}
});
// 访问reactive的值不需要.value
console.log(state.count);
state.count++;
// 在模板中自动解包,不需要.value
// <template>
// <div>{{ count }}</div>
// <div>{{ state.count }}</div>
// </template>
4. toRefs的使用
当需要从reactive对象中解构属性时,使用toRefs保持响应性。
javascript
import { reactive, toRefs } from 'vue';
export default {
setup() {
const state = reactive({
count: 0,
name: 'Vue 3',
isActive: true
});
// 不推荐 - 失去响应性
// const { count, name } = state;
// 推荐 - 使用toRefs保持响应性
const { count, name, isActive } = toRefs(state);
const increment = () => {
count.value++;
};
return {
count,
name,
isActive,
increment
};
}
};
计算属性与侦听器
5. computed计算属性
computed用于创建计算属性,支持getter和setter。
javascript
import { ref, computed } from 'vue';
export default {
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
// 只读计算属性
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value;
});
// 可写计算属性
const writableFullName = computed({
get() {
return firstName.value + ' ' + lastName.value;
},
set(value) {
const [first, last] = value.split(' ');
firstName.value = first;
lastName.value = last;
}
});
return {
firstName,
lastName,
fullName,
writableFullName
};
}
};
6. watch与watchEffect
watch和watchEffect用于侦听数据变化。
javascript
import { ref, reactive, watch, watchEffect } from 'vue';
export default {
setup() {
const count = ref(0);
const user = reactive({
name: 'Vue',
age: 3
});
// watchEffect - 自动追踪依赖
watchEffect(() => {
console.log(`Count is: ${count.value}`);
console.log(`User is: ${user.name}`);
});
// watch - 显式指定侦听源
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
// 侦听多个源
watch([count, () => user.name], ([newCount, newName], [oldCount, oldName]) => {
console.log(`Count: ${oldCount} -> ${newCount}, Name: ${oldName} -> ${newName}`);
});
// watch的配置选项
watch(
() => user.name,
(newValue) => {
console.log(`Name changed to: ${newValue}`);
},
{
immediate: true, // 立即执行
deep: true // 深度侦听
}
);
return { count, user };
}
};
生命周期钩子
7. 生命周期钩子的使用
Composition API中的生命周期钩子以on开头。
javascript
import {
onMounted,
onUpdated,
onUnmounted,
onBeforeMount,
onBeforeUpdate,
onBeforeUnmount
} from 'vue';
export default {
setup() {
onBeforeMount(() => {
console.log('组件挂载前');
});
onMounted(() => {
console.log('组件已挂载');
// 可以在这里访问DOM
});
onBeforeUpdate(() => {
console.log('组件更新前');
});
onUpdated(() => {
console.log('组件已更新');
});
onBeforeUnmount(() => {
console.log('组件卸载前');
});
onUnmounted(() => {
console.log('组件已卸载');
// 清理工作
});
return {};
}
};
自定义组合函数
8. 创建可复用的逻辑
自定义组合函数是Composition API的核心优势,让我们能够提取和复用逻辑。
javascript
// useCounter.js - 计数器逻辑
import { ref, computed } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const reset = () => {
count.value = initialValue;
};
const double = computed(() => count.value * 2);
return {
count,
increment,
decrement,
reset,
double
};
}
// useMouse.js - 鼠标位置追踪
import { ref, onMounted, onUnmounted } from 'vue';
export function useMouse() {
const x = ref(0);
const y = ref(0);
const update = (event) => {
x.value = event.pageX;
y.value = event.pageY;
};
onMounted(() => {
window.addEventListener('mousemove', update);
});
onUnmounted(() => {
window.removeEventListener('mousemove', update);
});
return { x, y };
}
// 在组件中使用
import { useCounter, useMouse } from './composables';
export default {
setup() {
const { count, increment, decrement, double } = useCounter(10);
const { x, y } = useMouse();
return {
count,
increment,
decrement,
double,
x,
y
};
}
};
依赖注入
9. provide与inject
provide和inject用于跨组件层级传递数据。
javascript
// 父组件
import { provide, ref } from 'vue';
export default {
setup() {
const theme = ref('dark');
const user = ref({
name: 'Vue User',
role: 'admin'
});
// 提供数据
provide('theme', theme);
);
provide('user', user);
return { theme };
}
};
// 子组件
import { inject } from 'vue';
export default {
setup() {
// 注入数据
const theme = inject('theme');
const user = inject('user');
// 提供默认值
const config = inject('config', {
debug: false,
version: '1.0'
});
return { theme, user, config };
}
};
模板引用
10. 使用ref获取DOM元素
在Composition API中使用ref获取模板引用。
javascript
import { ref, onMounted } from 'vue';
export default {
setup() {
// 创建模板引用
const inputRef = ref(null);
const listRef = ref(null);
onMounted(() => {
// 访问DOM元素
inputRef.value.focus();
// 访问组件实例
console.log(listRef.value.items);
});
const focusInput = () => {
inputRef.value.focus();
};
return {
inputRef,
listRef,
focusInput
};
}
};
// 模板中使用
// <template>
// <input ref="inputRef" />
// <MyList ref="listRef" />
// </template>
实战案例
11. 表单处理组合函数
javascript
// useForm.js
import { ref, reactive } from 'vue';
export function useForm(initialValues, validationRules) {
const values = reactive({ ...initialValues });
const errors = reactive({});
const touched = reactive({});
const validate = () => {
let isValid = true;
for (const field in validationRules) {
const rules = validationRules[field];
const value = values[field];
for (const rule of rules) {
if (rule.required && !value) {
errors[field] = rule.message || '此字段必填';
isValid = false;
break;
}
if (rule.pattern && !rule.pattern.test(value)) {
errors[field] = rule.message || '格式不正确';
isValid = false;
break;
}
if (rule.validator && !rule.validator(value)) {
errors[field] = rule.message || '验证失败';
isValid = false;
break;
}
}
}
return isValid;
};
const handleChange = (field) => (event) => {
values[field] = event.target.value;
touched[field] = true;
if (errors[field]) {
validate();
}
};
const handleBlur = (field) => () => {
touched[field] = true;
validate();
};
const reset = () => {
Object.assign(values, initialValues);
Object.keys(errors).forEach(key => {
errors[key] = '';
});
Object.keys(touched).forEach(key => {
touched[key] = false;
});
};
const submit = (callback) => () => {
if (validate()) {
callback(values);
}
};
return {
values,
errors,
touched,
handleChange,
handleBlur,
validate,
reset,
submit
};
}
// 使用示例
export default {
setup() {
const { values, errors, handleChange, handleBlur, submit } = useForm(
{
username: '',
email: '',
password: ''
},
{
username: [
{ required: true, message: '用户名必填' },
{ pattern: /^[a-zA-Z0-9_]{3,20}$/, message: '用户名格式不正确' }
],
email: [
{ required: true, message: '邮箱必填' },
{ pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, message: '邮箱格式不正确' }
],
password: [
{ required: true, message: '密码必填' },
{ validator: (value) => value.length >= 6, message: '密码至少6位' }
]
}
);
const handleSubmit = submit((formData) => {
console.log('表单提交:', formData);
// 发送API请求
});
return {
values,
errors,
handleChange,
handleBlur,
handleSubmit
};
}
};
12. 异步数据获取组合函数
javascript
// useAsyncData.js
import { ref, onMounted } from 'vue';
export function useAsyncData(fetchFn, options = {}) {
const {
immediate = true,
initialData = null,
onSuccess,
onError
} = options;
const data = ref(initialData);
const loading = ref(false);
const error = ref(null);
const execute = async (...args) => {
loading.value = true;
error.value = null;
try {
const result = await fetchFn(...args);
data.value = result;
if (onSuccess) {
onSuccess(result);
}
return result;
} catch (err) {
error.value = err;
if (onError) {
onError(err);
}
throw err;
} finally {
loading.value = false;
}
};
if (immediate) {
onMounted(execute);
}
return {
data: data,
loading: loading,
error: error,
execute: execute,
refresh: execute
};
}
// 使用示例
export default {
setup() {
const { data, loading, error, refresh } = useAsyncData(
async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
{
immediate: true,
onSuccess: (data) => {
console.log('数据加载成功:', data);
},
onError: (error) => {
console.error('数据加载失败:', error);
}
}
);
return {
data,
loading,
error,
refresh
};
}
};
总结
Vue 3 Composition API为我们提供了更强大、更灵活的代码组织方式:
核心优势
- 逻辑复用:通过自定义组合函数轻松复用逻辑
- 代码组织:相关逻辑可以组织在一起,而不是分散在options中
- 类型推断:更好的TypeScript支持
- 灵活性:更灵活的代码组织方式
最佳实践
- 合理使用ref和reactive:基本类型用ref,复杂对象用reactive
- 提取组合函数:将可复用逻辑提取为独立的组合函数
- 保持单一职责:每个组合函数只负责一个功能
- 善用toRefs:解构reactive对象时使用toRefs保持响应性
- 合理使用生命周期:在setup中正确使用生命周期钩子
学习路径
- 掌握setup函数和响应式API
- 学习computed和watch的使用
- 理解生命周期钩子
- 实践自定义组合函数
- 掌握依赖注入和模板引用
Composition API不仅是一种新的API,更是一种新的思维方式。它让我们能够以更函数式、更模块化的方式组织代码,提高了代码的可维护性和可测试性。开始在你的项目中使用Composition API吧,体验Vue 3带来的全新开发体验!
本文首发于掘金,欢迎关注我的专栏获取更多前端技术干货!