Vue3组合式函数的最佳实践

513 阅读5分钟

我最近与 Vue Storefront 团队进行了一次很好的讨论,关于编写组合式API的模式。在我们的系统中,组合式API负责存储主要的业务逻辑(如计算、操作、流程),因此它们是应用程序的重要组成部分。。

我很高兴现在来重构组合式API的方法,使它们易于维护、易于测试,并且真正有用。

在本文中,我将总结我们创建的思路,并将它们与我在几篇文章中读到的良好实践和设计模式结合起来。

因此,本文将分为三个部分:

  1. 通用设计模式
  2. 我的建议
  3. 进一步阅读

通用设计模式

在我看来,学习构建组合式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应遵循以下约定:

  1. 组合式函数文件名应以 use 开头,例如 useSomeAmazingFeature.ts
  2. 接收输入参数,可以是字符串等原始类型,也可以是 refs 和 getters,但需要使用 toValue 辅助函数
  3. 组合式API应返回一个 ref 值,可以在解构后访问,例如 const { x, y } = useMouse()
  4. 组合式API可以持有全局状态,可以在应用程序中访问和修改
  5. 组合式API 可以产生副作用,例如添加窗口事件监听器,但应在组件卸载时清理
  6. 组合式API 应仅在 <script setup>setup() 钩子中调用。它们还应在这些上下文中同步调用。在某些情况下,你也可以在生命周期钩子中调用它们,如 onMounted()
  7. 组合式API 可以在内部调用其他组合式API
  8. 组合式API 应该封装特定的逻辑,当过于复杂时,应该提取到单独的组合式API中,以便于测试

我的建议

我为工作项目和开源项目(如 NuxtAlgolia、NuxtCloudinary、NuxtMedusa)构建了多个组合式API ,因此基于这些经验,我想在上述约定中添加几点建议。

有状态或纯函数组合式API

最容易测试的函数,是那些不存储任何状态的函数(即简单的输入/输出函数)。例如一个负责将字节转换为人类可读值的组合式API 。它接收一个值并返回一个不同的值 - 它不存储任何状态。

组合式API的单元测试

我们希望为前端实现单元测试。在后端工作时,单元测试代码覆盖率非常有用,因为那里主要关注逻辑。然而,在前端,通常处理的是视觉效果。

因此,我们对整个组件进行单元测试可能不是最好的主意。因为我们基本上是在测试框架本身(如果按钮被按下,检查状态是否改变或模态框是否打开)。

由于我们已经将所有业务逻辑移到了 组合式API 中(基本上是 TypeScript 函数),它们非常容易进行测试,并且使我们的系统更加稳定。

组合式API 的作用域

一段时间以来,在 VueStorefront 中,我们开发了组合式API 的方法。在我们的方法中,我们使用组合式API 应用在电子商务的领域,如下所示:

const { cart, load, addItem, removeItem, remove, ... } = useCart()

它允许将领域封装在一个函数中。在更简单的示例中,如 useProductuseCategory,这相对容易实现和维护。然而,正如你在这里看到的 useCart 示例,当封装的领域包含比数据获取更多的逻辑时,这个组合式API 会变得越来越大,难以开发和维护。

在这一点上,我开始为 Nuxt 生态系统做出贡献,其中引入了不同的方法。在这种新方法中,每个 组合式函数 仅负责一件事。因此,与其构建一个巨大的 useCart 组合式函数,思路是为每个功能构建 组合式函数,例如 useAddToCartuseFetchCartuseRemovefromCart 等。

由于这一点,维护和测试这些 组合式API 应该容易得多。

进一步阅读

以上就是我的研究内容。如果你想进一步了解这个主题,务必查看以下文章:

原文:dev.to/jacobandrew…