组合式函数
在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
我们也称组合式函数为自定义hook
1.鼠标跟踪示例
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<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
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 }
}<script setup>
import { useFetch } from './fetch.js'
const { data, error } = useFetch('...')
</script>2.1 接受响应式状态
hook可以接受一个响应式数据,或者是一个getter函数
并且在hook函数中可以监听到值的改变,注意的是要在hook里把参数转化成响应式
见如下示例
import { watchEffect, toValue } from 'vue'
const useMouse = (value) => {
watchEffect(() => {
console.log(toValue(value))
})
}
export default useMouse<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() 工具函数来实现:
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 之后仍可以保持响应性:
// x 和 y 是两个 ref
const { x, y } = useMouse()而如果返回的是一个reactive(),可以将返回的对象用 reactive() 包装一次,这样其中的 ref 会被自动解包,例如:
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 用于确定当前活跃的组件实例的上下文。访问活跃的组件实例很有必要,这样才能:
- 将生命周期钩子注册到该组件实例上
- 将计算属性和监听器注册到该组件实例上,以便在该组件被卸载时停止监听,避免内存泄漏。
TIP
<script setup> 是唯一在调用 await 之后仍可调用组合式函数的地方。编译器会在异步操作之后自动为你恢复当前的组件实例。
