我最近与 Vue Storefront 团队进行了一次很好的讨论,关于编写组合式API的模式。在我们的系统中,组合式API负责存储主要的业务逻辑(如计算、操作、流程),因此它们是应用程序的重要组成部分。。
我很高兴现在来重构组合式API的方法,使它们易于维护、易于测试,并且真正有用。
在本文中,我将总结我们创建的思路,并将它们与我在几篇文章中读到的良好实践和设计模式结合起来。
因此,本文将分为三个部分:
- 通用设计模式
- 我的建议
- 进一步阅读
通用设计模式
在我看来,学习构建组合式API的最佳资料是 Vue.js 文档,你可以在这里查看。
基本组合式函数
Vue 文档展示了以下 useMouse 的示例:
// mouse.js
import { ref, onMounted, onUnmounted } from 'vue'
// 按惯例,组合式函数名称以 "use" 开头
export function useMouse() {
// 由组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数 可以随着时间的推移更新其管理的状态
function update(event) {
x.value = event.pageX
y.value = event.pageY
}
// 组合式函数还可以挂钩到其所有者组件的生命周期,以设置和清理副作用
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
// 将管理的状态作为返回值暴露
return { x, y }
}
在组件中如下使用:
<script setup>
import { useMouse } from './mouse.js'
const { x, y } = useMouse()
</script>
<template>Mouse position is at: {{ x }}, {{ y }}</template>
异步组合式API
对于获取数据,Vue 推荐以下的结构:
import { ref, watchEffect, toValue } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
watchEffect(() => {
// 在获取之前重置状态..
data.value = null
error.value = null
// toValue() 解包潜在的 ref 或 getter
fetch(toValue(url))
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
})
return { data, error }
}
在组件中如下使用:
<script setup>
import { useFetch } from './useFetch.ts'
const { data, error } = useFetch('...')
</script>
组合式API约定
根据上述示例,所有组合式API应遵循以下约定:
- 组合式函数文件名应以 use 开头,例如
useSomeAmazingFeature.ts - 接收输入参数,可以是字符串等原始类型,也可以是 refs 和 getters,但需要使用 toValue 辅助函数
- 组合式API应返回一个 ref 值,可以在解构后访问,例如
const { x, y } = useMouse() - 组合式API可以持有全局状态,可以在应用程序中访问和修改
- 组合式API 可以产生副作用,例如添加窗口事件监听器,但应在组件卸载时清理
- 组合式API 应仅在
<script setup>或setup()钩子中调用。它们还应在这些上下文中同步调用。在某些情况下,你也可以在生命周期钩子中调用它们,如onMounted()。 - 组合式API 可以在内部调用其他组合式API
- 组合式API 应该封装特定的逻辑,当过于复杂时,应该提取到单独的组合式API中,以便于测试
我的建议
我为工作项目和开源项目(如 NuxtAlgolia、NuxtCloudinary、NuxtMedusa)构建了多个组合式API ,因此基于这些经验,我想在上述约定中添加几点建议。
有状态或纯函数组合式API
最容易测试的函数,是那些不存储任何状态的函数(即简单的输入/输出函数)。例如一个负责将字节转换为人类可读值的组合式API 。它接收一个值并返回一个不同的值 - 它不存储任何状态。
组合式API的单元测试
我们希望为前端实现单元测试。在后端工作时,单元测试代码覆盖率非常有用,因为那里主要关注逻辑。然而,在前端,通常处理的是视觉效果。
因此,我们对整个组件进行单元测试可能不是最好的主意。因为我们基本上是在测试框架本身(如果按钮被按下,检查状态是否改变或模态框是否打开)。
由于我们已经将所有业务逻辑移到了 组合式API 中(基本上是 TypeScript 函数),它们非常容易进行测试,并且使我们的系统更加稳定。
组合式API 的作用域
一段时间以来,在 VueStorefront 中,我们开发了组合式API 的方法。在我们的方法中,我们使用组合式API 应用在电子商务的领域,如下所示:
const { cart, load, addItem, removeItem, remove, ... } = useCart()
它允许将领域封装在一个函数中。在更简单的示例中,如 useProduct 或 useCategory,这相对容易实现和维护。然而,正如你在这里看到的 useCart 示例,当封装的领域包含比数据获取更多的逻辑时,这个组合式API 会变得越来越大,难以开发和维护。
在这一点上,我开始为 Nuxt 生态系统做出贡献,其中引入了不同的方法。在这种新方法中,每个 组合式函数 仅负责一件事。因此,与其构建一个巨大的 useCart 组合式函数,思路是为每个功能构建 组合式函数,例如 useAddToCart、useFetchCart、useRemovefromCart 等。
由于这一点,维护和测试这些 组合式API 应该容易得多。
进一步阅读
以上就是我的研究内容。如果你想进一步了解这个主题,务必查看以下文章: