Skip to content

组合式函数

在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。

我们也称组合式函数为自定义hook

1.鼠标跟踪示例

js
import { reactive, onMounted, onUnmounted } from 'vue'
const useMouse = () => {
  const point = reactive({
    x: 0,
    y: 0
  })
  function update(e) {
    point.x = e.pageX
    point.y = e.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))
  return point
}

export default useMouse
vue
<template>
  <div>{{ x }},{{ y }}</div>
</template>

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

2.异步状态示例

在项目中,一定会封装异步请求的函数,而且需要不同的url参数,我们可以简要封装一个异步请求的hook

js
import { ref } from 'vue'

export function useFetch(url) {
  const data = ref(null)
  const error = ref(null)

  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err))

  return { data, error }
}
vue
<script setup>
import { useFetch } from './fetch.js'

const { data, error } = useFetch('...')
</script>

2.1 接受响应式状态

hook可以接受一个响应式数据,或者是一个getter函数

并且在hook函数中可以监听到值的改变,注意的是要在hook里把参数转化成响应式

见如下示例

js
import { watchEffect, toValue } from 'vue'

const useMouse = (value) => {
  watchEffect(() => {
    console.log(toValue(value))
  })
}

export default useMouse
vue
<template>
  <div>
    <button @click="num++">Add</button>
  </div>
</template>

<script setup>
import useMouse from '../hooks/useMouse'
import { ref } from 'vue'
const num = ref(11)
useMouse(num)
</script>

3.约定和最佳实践

3.1 命名

组合式函数约定用驼峰命名法命名,并以“use”作为开头。

3.2 输入参数

即便不依赖于 ref 或 getter 的响应性,组合式函数也可以接收它们作为参数。如果你正在编写一个可能被其他开发者使用的组合式函数,最好处理一下输入参数是 ref 或 getter 而非原始值的情况。可以利用 toValue() 工具函数来实现:

js
import { toValue } from 'vue'

function useFeature(maybeRefOrGetter) {
  // 如果 maybeRefOrGetter 是一个 ref 或 getter,
  // 将返回它的规范化值。
  // 否则原样返回。
  const value = toValue(maybeRefOrGetter)
}

如果你的组合式函数在输入参数是 ref 或 getter 的情况下创建了响应式 effect,为了让它能够被正确追踪,请确保要么使用 watch() 显式地监视 ref 或 getter,要么在 watchEffect() 中调用 toValue()

3.3 返回值

在hook的返回值中,如果需要返回一个变量,最好返回一个ref()响应式变量,而不是reactive()

这样该对象在组件中被解构为 ref 之后仍可以保持响应性:

js
// x 和 y 是两个 ref
const { x, y } = useMouse()

而如果返回的是一个reactive(),可以将返回的对象用 reactive() 包装一次,这样其中的 ref 会被自动解包,例如:

js
const mouse = reactive(useMouse())
// mouse.x 链接到了原来的 x ref
console.log(mouse.x)

3.4 副作用

在组合式函数中可以执行副作用 (例如:添加 DOM 事件监听器或者请求数据),但请注意以下规则:

  • 确保在 onUnmounted() 时清理副作用。举例来说,如果一个组合式函数设置了一个事件监听器,它就应该在 onUnmounted() 中被移除 (就像我们在 useMouse() 示例中看到的一样)。当然也可以像之前的 useEventListener() 示例那样,使用一个组合式函数来自动帮你做这些事。
  • 如果你的应用用到了服务端渲染 (SSR),请确保在组件挂载后才调用的生命周期钩子中执行 DOM 相关的副作用,例如:onMounted()。这些钩子仅会在浏览器中被调用,因此可以确保能访问到 DOM。

3.5 使用限制

组合式函数只能在 <script setup>setup() 钩子中被调用。在这些上下文中,它们也只能被同步调用。在某些情况下,你也可以在像 onMounted() 这样的生命周期钩子中调用它们。

这些限制很重要,因为这些是 Vue 用于确定当前活跃的组件实例的上下文。访问活跃的组件实例很有必要,这样才能:

  1. 将生命周期钩子注册到该组件实例上
  2. 将计算属性和监听器注册到该组件实例上,以便在该组件被卸载时停止监听,避免内存泄漏。

TIP

<script setup> 是唯一在调用 await 之后仍可调用组合式函数的地方。编译器会在异步操作之后自动为你恢复当前的组件实例。

MIT Licensed