从零基础到最佳实践:Vue.js 系列(6/10):《Composition API(组合式 API)》
引言
Vue.js 作为前端开发领域的热门框架之一,在 Vue 3 中引入了 Composition API(组合式 API),为开发者带来了全新的代码组织方式。相比传统的 Options API,Composition API 更加灵活,能够更好地应对复杂逻辑的开发需求。它通过将相关逻辑集中在一起,提升了代码的可读性和可维护性,成为现代 Vue 开发的标配。
本文将带你从零开始学习 Composition API,涵盖基础用法、进阶技巧以及大量实际开发场景。无论你是刚接触 Vue 的新手,还是希望深入掌握 Vue 3 的老手,这篇文章都能为你提供实用的知识和灵感。
一、Composition API 基础
1.1 什么是 setup
函数?
setup
函数是 Composition API 的核心,它是组件的入口点,在组件创建时被调用,用于定义响应式数据、计算属性、方法等。它接收两个参数:
props
:组件接收的外部属性。context
:包含attrs
(非 prop 属性)、slots
(插槽)和emit
(事件触发器)等。
简单示例:
<template><div>{{ count }} * 2 = {{ double }}</div><button @click="increment">加 1</button>
</template><script>
import { ref, computed } from 'vue';export default {setup() {const count = ref(0); // 响应式数据const double = computed(() => count.value * 2); // 计算属性const increment = () => { count.value++; }; // 方法return { count, double, increment }; // 返回给模板使用},
};
</script>
在这个例子中,setup
函数定义了 count
(计数器)、double
(计算属性)和 increment
(加 1 方法),并通过 return
将它们暴露给模板。
1.2 响应式数据:ref
和 reactive
Vue 的响应式系统是其核心特性之一,Composition API 提供了 ref
和 reactive
来创建响应式数据。
ref
:用于基本数据类型(如数字、字符串)的响应式引用。访问和修改时需要使用.value
。reactive
:用于复杂数据类型(如对象、数组)的响应式引用,不需要.value
。
示例:
<template><div>{{ count }} - {{ user.name }}</div><button @click="count++">加 1</button><button @click="user.name = '李四'">改名</button>
</template><script>
import { ref, reactive } from 'vue';export default {setup() {const count = ref(0);const user = reactive({ name: '张三', age: 18 });return { count, user };},
};
</script>
注意:在模板中,ref
的值会自动解包,无需写 .value
。
1.3 计算属性:computed
computed
用于创建依赖于其他响应式数据的计算属性,只有当依赖发生变化时才会重新计算。
示例:
<template><div>总价:{{ totalPrice }}</div>
</template><script>
import { ref, computed } from 'vue';export default {setup() {const price = ref(100);const quantity = ref(2);const totalPrice = computed(() => price.value * quantity.value);return { totalPrice };},
};
</script>
1.4 侦听器:watch
和 watchEffect
watch
:显式监听某个数据源的变化,并执行回调函数。watchEffect
:自动追踪依赖的变化并执行副作用。
示例:
<template><div>{{ count }}</div><button @click="count++">加 1</button>
</template><script>
import { ref, watch, watchEffect } from 'vue';export default {setup() {const count = ref(0);// watch:显式监听 countwatch(count, (newVal, oldVal) => {console.log(`count 从 ${oldVal} 变为 ${newVal}`);});// watchEffect:自动追踪依赖watchEffect(() => {console.log(`count 当前值:${count.value}`);});return { count };},
};
</script>
二、进阶用法
2.1 生命周期钩子
Composition API 提供了与 Options API 类似的生命周期钩子,如 onMounted
、onUpdated
等,在 setup
中使用。
示例:
<template><div>{{ message }}</div>
</template><script>
import { ref, onMounted, onUnmounted } from 'vue';export default {setup() {const message = ref('Hello');onMounted(() => {console.log('组件已挂载');});onUnmounted(() => {console.log('组件已卸载');});return { message };},
};
</script>
2.2 依赖注入:provide
和 inject
provide
和 inject
用于跨组件传递数据,特别适合祖孙组件通信。
示例:
<!-- 父组件 -->
<template><Child />
</template><script>
import { provide } from 'vue';
import Child from './Child.vue';export default {setup() {provide('config', { theme: 'dark' });},components: { Child },
};
</script><!-- 子组件 -->
<template><div>主题:{{ theme }}</div>
</template><script>
import { inject } from 'vue';export default {setup() {const config = inject('config');return { theme: config.theme };},
};
</script>
2.3 自定义 Hooks
自定义 Hooks 是 Composition API 的亮点之一,可以封装可复用的逻辑。
示例:鼠标位置追踪 Hook
// 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 };
}
使用:
<template><div>鼠标位置:{{ x }}, {{ y }}</div>
</template><script>
import { useMouse } from './useMouse';export default {setup() {const { x, y } = useMouse();return { x, y };},
};
</script>
三、实际开发应用场景
3.1 表单处理与验证
案例:用户注册表单
<template><form @submit.prevent="submit"><input v-model="form.username" placeholder="用户名" /><input v-model="form.password" type="password" placeholder="密码" /><div v-if="errors.username">{{ errors.username }}</div><div v-if="errors.password">{{ errors.password }}</div><button type="submit" :disabled="hasErrors">提交</button></form>
</template><script>
import { reactive, computed } from 'vue';export default {setup() {const form = reactive({username: '',password: '',});const errors = computed(() => {const errs = {};if (!form.username) errs.username = '用户名不能为空';if (form.password.length < 6) errs.password = '密码至少 6 位';return errs;});const hasErrors = computed(() => Object.keys(errors.value).length > 0);const submit = () => {if (!hasErrors.value) {console.log('提交成功:', form);}};return { form, errors, hasErrors, submit };},
};
</script>
3.2 数据请求与状态管理
案例:获取用户列表
<template><div v-if="loading">加载中...</div><div v-else-if="error">{{ error }}</div><ul v-else><li v-for="user in users" :key="user.id">{{ user.name }}</li></ul>
</template><script>
import { ref, onMounted } from 'vue';export default {setup() {const users = ref([]);const loading = ref(true);const error = ref(null);const fetchUsers = async () => {try {const res = await fetch('https://jsonplaceholder.typicode.com/users');users.value = await res.json();} catch (err) {error.value = '加载失败:' + err.message;} finally {loading.value = false;}};onMounted(fetchUsers);return { users, loading, error };},
};
</script>
3.3 组件通信与状态共享
案例:主题切换
<!-- App.vue -->
<template><div :class="theme"><ThemeToggle /><Child /></div>
</template><script>
import { ref, provide } from 'vue';
import ThemeToggle from './ThemeToggle.vue';
import Child from './Child.vue';export default {setup() {const theme = ref('light');provide('theme', theme);return { theme };},components: { ThemeToggle, Child },
};
</script><!-- ThemeToggle.vue -->
<template><button @click="toggleTheme">切换主题</button>
</template><script>
import { inject } from 'vue';export default {setup() {const theme = inject('theme');const toggleTheme = () => {theme.value = theme.value === 'light' ? 'dark' : 'light';};return { toggleTheme };},
};
</script><!-- Child.vue -->
<template><div>当前主题:{{ theme }}</div>
</template><script>
import { inject } from 'vue';export default {setup() {const theme = inject('theme');return { theme };},
};
</script>
3.4 动态表格与分页
案例:带分页的用户表格
<template><div><table><thead><tr><th>ID</th><th>姓名</th><th>邮箱</th></tr></thead><tbody><tr v-for="user in paginatedUsers" :key="user.id"><td>{{ user.id }}</td><td>{{ user.name }}</td><td>{{ user.email }}</td></tr></tbody></table><button @click="prevPage" :disabled="currentPage === 1">上一页</button><span>第 {{ currentPage }} 页</span><button @click="nextPage" :disabled="currentPage === totalPages">下一页</button></div>
</template><script>
import { ref, computed, onMounted } from 'vue';export default {setup() {const users = ref([]);const currentPage = ref(1);const pageSize = 5;const totalPages = computed(() => Math.ceil(users.value.length / pageSize));const paginatedUsers = computed(() => {const start = (currentPage.value - 1) * pageSize;const end = start + pageSize;return users.value.slice(start, end);});const fetchUsers = async () => {const res = await fetch('https://jsonplaceholder.typicode.com/users');users.value = await res.json();};const prevPage = () => { if (currentPage.value > 1) currentPage.value--; };const nextPage = () => { if (currentPage.value < totalPages.value) currentPage.value++; };onMounted(fetchUsers);return { paginatedUsers, currentPage, totalPages, prevPage, nextPage };},
};
</script>
四、优化技巧
4.1 性能优化
- 减少不必要计算:使用
computed
缓存计算结果,避免重复计算。 - 控制
watchEffect
:尽量减少依赖项,防止频繁触发。 - 解构响应式对象:使用
toRefs
保持响应性。
示例:
import { reactive, toRefs } from 'vue';const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state); // 解构后仍保持响应性
4.2 代码组织与重用
- 抽离逻辑:将复杂逻辑封装为自定义 Hooks。
- 模块化:将功能按模块拆分到单独文件中。
示例:计数器 Hook
// useCounter.js
import { ref } from 'vue';export function useCounter(initialValue = 0) {const count = ref(initialValue);const increment = () => { count.value++; };const decrement = () => { count.value--; };return { count, increment, decrement };
}
使用:
<template><div>{{ count }}</div><button @click="increment">加 1</button><button @click="decrement">减 1</button>
</template><script>
import { useCounter } from './useCounter';export default {setup() {const { count, increment, decrement } = useCounter(10);return { count, increment, decrement };},
};
</script>
4.3 错误处理
在异步操作中添加完善的错误处理逻辑。
示例:
<template><div v-if="error">{{ error }}</div><div v-else>{{ data }}</div>
</template><script>
import { ref, onMounted } from 'vue';export default {setup() {const data = ref(null);const error = ref(null);const fetchData = async () => {try {const res = await fetch('https://api.example.com/data');if (!res.ok) throw new Error('网络错误');data.value = await res.json();} catch (err) {error.value = err.message;}};onMounted(fetchData);return { data, error };},
};
</script>
五、未来趋势展望
随着 Vue 3 的普及,Composition API 将成为 Vue 开发的主流方式。它与 TypeScript 的深度结合将为大型项目提供更好的类型支持。此外,社区可能会推出更多基于 Composition API 的插件和工具,如状态管理库、表单处理库等,进一步丰富 Vue 生态。
未来,Composition API 还可能与 Vite 等现代构建工具深度整合,提升开发体验和性能。
六、总结
Composition API 是 Vue 3 的核心特性之一,它不仅提升了代码的灵活性,还为开发者提供了强大的工具来应对复杂场景。通过本文的学习,你应该能够掌握从基础到进阶的用法,并在实际开发中灵活运用。
希望你能在实践中不断探索 Composition API 的潜力,构建出高效、可维护的前端应用!
这篇文章详细介绍了 Composition API 的方方面面,包含多个实用案例和优化建议,适合不同层次的开发者学习和参考。希望对你有所帮助!