网络日志

Vue3 Composable最佳实践(一)

截至目前,Composable(组合式函数)应该是在VUE 3应用程序中组织业务逻辑最佳的方法。它让我们可以把一些小块的通用逻辑进行抽离、复用,使我们的代码更易于编写、阅读和维护。由于这种编写VUE代码的方式相对较新,因此您可能想知道编写组合式函数的最佳实践是什么呢?本系列教程可以作为您和您的团队在进行组合式开发过程中的参考指南。我们将涵盖以下内容:

  • 1.如何通过选项对象参数使您的组合更加可配置; 👈 本篇主题
  • 2.使用ref和unref使我们的参数更加灵活;
  • 3.如何使你的返回值更有用;
  • 4.为什么从接口定义开始可以使你的组合式函数更强大;
  • 5.如何使用异步代码而无需“等待” - 使您的代码更易于理解;

不过,首先,我们需要确保我们对组合式函数的理解是一致的。我们先花点时间解释一下什么是组合式函数。

什么是Composable-组合式函数?

根据官方文档说明,在 Vue 应用的概念中,“组合式函数”是一个利用 Vue 组合式 API 来封装和复用有状态逻辑的函数。这就意味着,任何有状态逻辑,并且使用了响应式处理的逻辑都可以转换成组合式函数。这和我们平时抽离封装的公共方法还是有一些区别的。我们封装的公共方法往往是无状态的:它在接收一些输入后立刻返回所期望的输出。而组合式函数往往是和状态逻辑关联的。

让我们看看官方给出的useMouse这个组合式函数:

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 }
}

我们把状态定义为refs,当鼠标移动的时候我们更新这个状态。通过返回xy,我们可以在任何组件中使用它们,甚至我们还可以把多个组合式函数嵌套使用。

当我们在组件中使用时

<template>
  X: {{ x }} Y: {{ y }}
</template>

<script setup>
  import { useMouse } from './useMouse';
  const { x, y } = useMouse();
</script>

如您所见,通过使用useMouse我们可以轻松的复用这个逻辑。仅仅很少的代码,我们就可以在组件中获取鼠标坐标状态。

现在我们对组合式函数有了相同的认识,让我们看一下可以帮助我们编写更好的组合式函数的第一个方法吧。

选项对象参数

大部分组合式函数都会有一个或两个必须的参数,然后有一系列可选的参数来帮助进行一些额外的配置。在配置组合式函数时,我们可以将一系列的可选配置放到一个选项对象参数中,而不是一长串参数的形式。

// 使用选项对象参数形式
const title = useTitle('A new title', { titleTemplate: '>> %s <<' });
// Title is now ">> A new title <<"

// 使用多参数形式
const title = useTitle('A new title', '>> %s <<');
// Title is now ">> A new title <<"

选项对象参数的形式可以给我们带来一些便利:首先,我们不必记住参数的正确顺序,特别是参数很多的时候。虽然现在我们可以通过Typescript和编辑器提示功能来避免这类问题,但是通过这种方式仍然是有差别的。使用Javascript对象,参数的顺序就不那么重要了。(当然,这也要求我们的函数定义需要清晰明了,我们后面会谈)其次,代码更可读取,因为我们知道该选项的作用是什么。第三,代码扩展性更好,以后添加新的选项要容易得多。这既适用于为组合式函数本身添加新选项,又适用于嵌套使用时参数传递。

因此,使用对象参数更加友好,但是我们该如何来实现呢?

在组合式函数中的实现

现在让我们看下如何在组合式函数中使用选项对象参数。我们来给上面的useMouse进行一些扩展:

export function useMouse(options) {
  const {
    asArray = false,
    throttle = false,
  } = options;

  // ...
};

useMouse本身没有必传参数,所以我们直接给它增加一个options参数来进行一些额外的配置。通过解构,我们可以访问所有的选传参数,并且为每个参数设置了默认值,这就避免了有些不需要额外配置的调用时没有传入可选参数的情况。

现在让我们来看两个VueUse上面的组合式函数是如何使用这个模式的。VueUse是一个服务于Vue3的组合式函数的常用工具集,它的初衷就是将一切原本并不支持响应式的JS API变得支持响应式,省去程序员自己写相关代码。

我们先来看useTitle,然后再看一下useRefHistory是如何实现的。

举例-useTitle

useTitle的作用非常简单,就是用来更新页面的标题。

const title = useTitle('Initial Page Title');
// Title: "Initial Page Title"

title.value = 'New Page Title';
// Title: "New Page Title"

它也有几个选择参数,来促进额外的灵活性。我们可以传入titleTemplate作为模版,并且通过observe来将其设置称为具备观察性(内部通过MutationObserver实现):

const title = useTitle('Initial Page Title', {
  titleTemplate: '>> %s <<',
  observe: true,
});
// Title: ">> Initial Page Title <<"

title.value = 'New Page Title';
// Title: ">> New Page Title <<"

当我们查看它的源码的时候可以看到以下处理

export function useTitle(newTitle, options) {
  const {
    document = defaultDocument,
    observe = false,
    titleTemplate = '%s',
  } = options;
  
  // ...
}

useTitle包含一个必传的参数,以及一个可选参数对象。正如我们上面描述的那样,它完全是按照这个模式来实现的。接下来,让我们看一下一个更复杂的组合式函数是如何使用选项对象模式的。

举例-useRefHistory

useRefHistory可以帮助我们追踪一个响应式变量的所有更改,可以让我们轻松的执行撤销和恢复的操作。

// Set up the count ref and track it
const count = ref(0);
const { undo } = useRefHistory(count);

// Increment the count
count.value++;

// Log out the count, undo, and log again
console.log(counter.value); // 1
undo();
console.log(counter.value); // 0

它支持设置许多不同的配置选择

{
  deep: false,
  flush: 'pre',
  capacity: -1,
  clone: false,
  // ...
}

如果想知道这些选项参数的完整列表和对应的功能,可以去查看相关文档,在此不再赘述。

我们可以将选项对象作为第二个参数传递,以进一步配置该组合函数的行为,与我们上一个示例相同:

export function useRefHistory(source, options) {
  const {
    deep = false,
    flush = 'pre',
    eventFilter,
  } = options;
 
  // ...
}

我们可以看到它内部仅仅解构出了一部分参数值,这是因为useRefHistory内部依赖了useManualHistory这个组合式函数,其他的选项参数将在后面透传给useManualHistory时进行展开合并。

// ...

const manualHistory = useManualRefHistory(
  source,
  {
    // Pass along the options object to another composable
    ...options,
    clone: options.clone || deep,
    setSource,
  },
);

// ...

这也和我们前面说到的内容相符:组合式函数可以嵌套使用。

小结

本文是“组合式函数最佳实践”的第一部分。我们研究了如何通过选项对象参数提升组合式函数的灵活性。无须担心参数顺序不对导致的问题,并且可以灵活进行配置项的增加扩展。我们不仅仅研究了这个模式本身,我们还通过VueUse中的useTitleuseRefHistory来学习了如何实现此模式。它们略有不同,但是这个模式本身就是很简单的,我们通过它能做到也是有限的。

下一篇我们将介绍如何通过ref和unref使我们的参数更加灵活

// Works if we give it a ref we already have
const countRef = ref(2);
useCount(countRef);

// Also works if we give it just a number
const countRef = useRef(2);

这增加了灵活性,使我们能够在应用程序中的更多情况下使用我们的组合。