Skip to content

组合式函数

组合式函数又叫做自定义 hook(学过 React Hooks 的同学不要笑~),它可以方便我们实现 SFC 组件之间的有状态逻辑的封装和共享。例如,项目的多个 SFC 都需要获取鼠标在网页上的坐标,为了实现“鼠标位置”逻辑的复用,我们可以把相关的代码封装成名为 useMouse 的组合式函数,并在需要的时候直接复用此函数即可得到鼠标的位置。

image-20240822212802636

🚨 注意 🚨

组合式函数必须以 use 开头!

封装鼠标跟踪器

在 SFC 中实现鼠标跟踪器

vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const x = ref(0)
const y = ref(0)

const update = (e) => {
  x.value = e.pageX
  y.value = e.pageY
}

// 在组件挂载和卸载时,绑定和移除 window 对象的 mousemove 事件监听程序
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script>

<template>
  <p>鼠标的位置 x: {{ x }},y: {{ y }}</p>
</template>

把鼠标跟踪器封装为组合式函数

为了实现鼠标跟踪器逻辑代码的复用,我们可以把与之有关的状态逻辑封装到独立的 js 模块中。在 src/ 下新建 hooks/ 文件夹,并在其下新建 mouse.js 的模块:

js
// 在头部区域导入需要用到的组合式 API
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  const update = (e) => {
    x.value = e.pageX
    y.value = e.pageY
  }

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

  // 向外暴露数据
  return { x, y }
}

然后,在需要用到鼠标位置的组件中,直接导入自定义的 useMouse hook 并使用即可:

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

<template>
  <p>鼠标的位置 x: {{ x }},y: {{ y }}</p>
</template>

把事件监听器封装为组合式函数

组合式函数可以嵌套使用。例如:把 mousemove 事件监听的绑定和移除的代码,也封装为自定义的组合式函数:

js
// 把事件绑定和移除的过程,封装为自定义 hook
export function useEvent(target, eventName, eventHandler) {
  onMounted(() => target.addEventListener(eventName, eventHandler))
  onUnmounted(() => target.removeEventListener(eventName, eventHandler))
}

然后,在 useMouse 中可以复用 useEvent 函数:

js
export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  const update = (e) => {
    x.value = e.pageX
    y.value = e.pageY
  }

  // 调用自定义 hook,在组件挂载和卸载后,自动绑定和移除“指定的”事件监听程序
  useEvent(window, 'mousemove', update)

  return { x, y }
}

封装秒数倒计时

使用倒计时的自定义 hook:

vue
<script setup>
import { useCountDown } from '../hooks/countDown.js'
const { count, isOver } = useCountDown(5, () => {
  console.log('基于路由 API 自动跳转到首页')
})
</script>

<template>
  <!-- 场景1:倒计时结束自动跳转 -->
  <h1>{{ count }} 秒后自动跳转</h1>

  <!-- 场景2:倒计时结束启用按钮 -->
  <button :disabled="!isOver">按钮</button>
</template>

src/hooks/ 目录下新建 countDown.js 模块:

js
import { ref, onMounted, onUnmounted } from 'vue'

export function useCountDown(initialCount, callback) {
  const count = ref(initialCount)
  const isOver = ref(false)
  let timerId

  // 组件挂载完成后,自动开启定时器
  onMounted(() => {
    timerId = setInterval(() => {
      count.value--
      if (count.value === 0) {
        // 定时器归零后,清除定时器、修改 isOver 状态、执行用户传入的回调
        clearInterval(timerId)
        isOver.value = true
        callback?.()
      }
    }, 1000)
  })

  // 组件被卸载后,自动清除定时器
  onUnmounted(() => {
    clearInterval(timerId)
  })

  return { count, isOver }
}

天不生夫子,万古长如夜