目录

什么是“组合式函数”?​

鼠标跟踪器示例​

异步状态示例​

约定和最佳实践​

命名​

输入参数​

返回值​

副作用​

使用限制​

通过抽取组合式函数改善代码结构

选项式 API 中使用组合式函数​

与其他模式的比较​

和 Mixin 的对比​

和无渲染组件的对比​

和 React Hooks 的对比​


什么是“组合式函数”?​

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

当构建前端应用时,我们常常需要复用公共任务的逻辑。例如为了在不同地方格式化时间,我们可能会抽取一个可复用的日期格式化函数。这个函数封装了无状态的逻辑:它在接收一些输入后立刻返回所期望的输出。复用无状态逻辑的库有很多,比如你可能已经用过的 lodash 或是 date-fns。

相比之下,有状态逻辑负责管理会随时间而变化的状态。一个简单的例子是跟踪当前鼠标在页面中的位置。在实际应用中,也可能是像触摸手势或与数据库的连接状态这样的更复杂的逻辑。

鼠标跟踪器示例​

如果我们要直接在组件中使用组合式 API 实现鼠标跟踪功能,它会是这样的:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'const x = ref(0)
const y = ref(0)function update(event) {x.value = event.pageXy.value = event.pageY
}onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
</script><template>Mouse position is at: {{ x }}, {{ y }}</template>

但是,如果我们想在多个组件中复用这个相同的逻辑呢?我们可以把这个逻辑以一个组合式函数的形式提取到外部文件中:

// 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.pageXy.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。现在,useMouse() 的功能可以在任何组件中轻易复用了。

更酷的是,你还可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。这使得我们可以像使用多个组件组合成整个应用一样,用多个较小且逻辑独立的单元来组合形成复杂的逻辑。实际上,这正是为什么我们决定将实现了这一设计模式的 API 集合命名为组合式 API。

举例来说,我们可以将添加和清除 DOM 事件监听器的逻辑也封装进一个组合式函数中:

// event.js
import { onMounted, onUnmounted } from 'vue'export function useEventListener(target, event, callback) {// 如果你想的话,// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素onMounted(() => target.addEventListener(event, callback))onUnmounted(() => target.removeEventListener(event, callback))
}

有了它,之前的 useMouse() 组合式函数可以被简化为:

// mouse.js
import { ref } from 'vue'
import { useEventListener } from './event'export function useMouse() {const x = ref(0)const y = ref(0)useEventListener(window, 'mousemove', (event) => {x.value = event.pageXy.value = event.pageY})return { x, y }
}

TIP

每一个调用 useMouse() 的组件实例会创建其独有的 xy 状态拷贝,因此他们不会互相影响。

异步状态示例​

useMouse() 组合式函数没有接收任何参数,因此让我们再来看一个需要接收一个参数的组合式函数示例。在做异步数据请求时,我们常常需要处理不同的状态:加载中、加载成功和加载失败。

<script setup>
import { ref } from 'vue'const data = ref(null)
const error = ref(null)fetch('...').then((res) => res.json()).then((json) => (data.value = json)).catch((err) => (error.value = err))
</script><template><div v-if="error">Oops! Error encountered: {{ error.message }}</div><div v-else-if="data">Data loaded:<pre>{{ data }}</pre></div><div v-else>Loading...</div>
</template>

如果在每个需要获取数据的组件中都要重复这种模式,那就太繁琐了。让我们把它抽取成一个组合式函数:

// fetch.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 }
}

现在我们在组件里只需要:

<script setup>
import { useFetch } from './fetch.js'const { data, error } = useFetch('...')
</script>

useFetch() 接收一个静态的 URL 字符串作为输入,所以它只执行一次请求,然后就完成了。但如果我们想让它在每次 URL 变化时都重新请求呢?那我们可以让它同时允许接收 ref 作为参数:

// fetch.js
import { ref, isRef, unref, watchEffect } from 'vue'export function useFetch(url) {const data = ref(null)const error = ref(null)function doFetch() {// 在请求之前重设状态...data.value = nullerror.value = null// unref() 解包可能为 ref 的值fetch(unref(url)).then((res) => res.json()).then((json) => (data.value = json)).catch((err) => (error.value = err))}if (isRef(url)) {// 若输入的 URL 是一个 ref,那么启动一个响应式的请求watchEffect(doFetch)} else {// 否则只请求一次// 避免监听器的额外开销doFetch()}return { data, error }
}

这个版本的 useFetch() 现在同时可以接收静态的 URL 字符串和 URL 字符串的 ref。当通过  isRef() 检测到 URL 是一个动态 ref 时,它会使用 watchEffect() 启动一个响应式的 effect。该 effect 会立刻执行一次,并在此过程中将 URL 的 ref 作为依赖进行跟踪。当 URL 的 ref 发生改变时,数据就会被重置,并重新请求。

约定和最佳实践​

命名​

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

输入参数​

尽管其响应性不依赖 ref,组合式函数仍可接收 ref 参数。如果编写的组合式函数会被其他开发者使用,你最好在处理输入参数时兼容 ref 而不只是原始的值。unref() 工具函数会对此非常有帮助:

import { unref } from 'vue'function useFeature(maybeRef) {// 若 maybeRef 确实是一个 ref,它的 .value 会被返回// 否则,maybeRef 会被原样返回const value = unref(maybeRef)
}

如果你的组合式函数在接收 ref 为参数时会产生响应式 effect,请确保使用 watch() 显式地监听此 ref,或者在 watchEffect() 中调用 unref() 来进行正确的追踪。

返回值​

你可能已经注意到了,我们一直在组合式函数中使用 ref() 而不是 reactive()。我们推荐的约定是组合式函数始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以保持响应性:

js

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

从组合式函数返回一个响应式对象会导致在对象解构过程中丢失与组合式函数内状态的响应性连接。与之相反,ref 则可以维持这一响应性连接。

如果你更希望以对象属性的形式来使用组合式函数中返回的状态,你可以将返回的对象用 reactive() 包装一次,这样其中的 ref 会被自动解包,例如:

const mouse = reactive(useMouse())
// mouse.x 链接到了原来的 x ref
console.log(mouse.x)
Mouse position is at: {{ mouse.x }}, {{ mouse.y }}

副作用​

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

  • 如果你的应用用到了服务端渲染 (SSR),请确保在组件挂载后才调用的生命周期钩子中执行 DOM 相关的副作用,例如:onMounted()。这些钩子仅会在浏览器中被调用,因此可以确保能访问到 DOM。

  • 确保在 onUnmounted() 时清理副作用。举例来说,如果一个组合式函数设置了一个事件监听器,它就应该在 onUnmounted() 中被移除 (就像我们在 useMouse() 示例中看到的一样)。当然也可以像之前的 useEventListener() 示例那样,使用一个组合式函数来自动帮你做这些事。

使用限制​

组合式函数在 <script setup> 或 setup() 钩子中,应始终被同步地调用。在某些场景下,你也可以在像 onMounted() 这样的生命周期钩子中使用他们。

这个限制是为了让 Vue 能够确定当前正在被执行的到底是哪个组件实例,只有能确认当前组件实例,才能够:

  1. 将生命周期钩子注册到该组件实例上

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

TIP

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

通过抽取组合式函数改善代码结构

抽取组合式函数不仅是为了复用,也是为了代码组织。随着组件复杂度的增高,你可能会最终发现组件多得难以查询和理解。组合式 API 会给予你足够的灵活性,让你可以基于逻辑问题将组件代码拆分成更小的函数:

<script setup>
import { useFeatureA } from './featureA.js'
import { useFeatureB } from './featureB.js'
import { useFeatureC } from './featureC.js'const { foo, bar } = useFeatureA()
const { baz } = useFeatureB(foo)
const { qux } = useFeatureC(baz)
</script>

在某种程度上,你可以将这些提取出的组合式函数看作是可以相互通信的组件范围内的服务。

选项式 API 中使用组合式函数​

如果你正在使用选项式 API,组合式函数必须在 setup() 中调用。且其返回的绑定必须在 setup() 中返回,以便暴露给 this 及其模板:

import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'export default {setup() {const { x, y } = useMouse()const { data, error } = useFetch('...')return { x, y, data, error }},mounted() {// setup() 暴露的属性可以在通过 `this` 访问到console.log(this.x)}// ...其他选项
}

与其他模式的比较​

和 Mixin 的对比​

Vue 2 的用户可能会对mixins选项比较熟悉。它也让我们能够把组件逻辑提取到可复用的单元里。然而 mixins 有三个主要的短板:

  1. 不清晰的数据来源:当使用了多个 mixin 时,实例上的数据属性来自哪个 mixin 变得不清晰,这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref + 解构模式的理由:让属性的来源在消费组件时一目了然。

  2. 命名空间冲突:多个来自不同作者的 mixin 可能会注册相同的属性名,造成命名冲突。若使用组合式函数,你可以通过在解构变量时对变量进行重命名来避免相同的键名。

  3. 隐式的跨 mixin 交流:多个 mixin 需要依赖共享的属性名来进行相互作用,这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入,像普通函数那样。

基于上述理由,我们不再推荐在 Vue 3 中继续使用 mixin。保留该功能只是为了项目迁移的需求和照顾熟悉它的用户。

和无渲染组件的对比​

在组件插槽一章中,我们讨论过了基于作用域插槽的​​​​​​​无渲染组件。我们甚至用它实现了一样的鼠标追踪器示例。

组合式函数相对于无渲染组件的主要优势是:组合式函数不会产生额外的组件实例开销。当在整个应用中使用时,由无渲染组件产生的额外组件实例会带来无法忽视的性能开销。

我们推荐在纯逻辑复用时使用组合式函数,在需要同时复用逻辑和视图布局时使用无渲染组件。

和 React Hooks 的对比​

如果你有 React 的开发经验,你可能注意到组合式函数和自定义 React hooks 非常相似。组合式 API 的一部分灵感正来自于 React hooks,Vue 的组合式函数也的确在逻辑组合能力上与 React hooks 相近。然而,Vue 的组合式函数是基于 Vue 细粒度的响应性系统,这和 React hooks 的执行模型有本质上的不同。

Vue——组合式函数相关推荐

  1. 【vue3】组合式函数

    vue3组合式函数 为什么引入?实现更好的状态复用(mixin) vue组合式函数 VS react的hook 跟着官网学习 组合式API 异步的数据请求例子 组合式 使用限制 本文主要参考vue.j ...

  2. vite+ts+vue组合式api-c端-移动端项目(保姆级教学)

    文章目录 一.项目创建准备工作 1.配置vite和ts默认项 2.配置路由命名 3.配置环境源env 4.二次封装axios 5.配置反向代理 6.测试请求接口 7.配置rem,配置移动端 8.配置v ...

  3. Vue3的组合式函数

    简言 今天来学习下vue3组合式函数用法,先甩官方链接:组合式函数. vue2的时候,以一个vue文件是按照vue给的模块文件来写的,也就是选项式API写法(一个文件里有template.script ...

  4. Vue3组合式函数最佳实践(一)

    截至目前,组合式函数应该是在VUE 3应用程序中组织业务逻辑最佳的方法. 它让我们可以把一些小块的通用逻辑进行抽离.复用,使我们的代码更易于编写.阅读和维护. 由于这种编写VUE代码的方式相对较新,因 ...

  5. 047Vue3组合式函数代替Mixin

    047Vue3组合式函数代替Mixin 组合式函数示例 // mouse.js import { ref, onMounted, onUnmounted } from 'vue'// 按照惯例,组合式 ...

  6. vue 全局函数的 定义与任意调用 (代码篇)

    文章目录 vue 全局函数的 定义与任意调用 方法一: 首先: 之后: 最后: 打印结果 · 截图: 方法二:[推荐] 代码:注意事项: vue 全局函数的 定义与任意调用 方法一: 首先: 随意新建 ...

  7. vue回调函数this指向问题

    郁闷了一天的一个问题,,在vue回调函数中使用this的话是无法引用当前vue实例中定义的对象的,可以在回调函数外定义let me = this.然后在回调函数中使用me 引用https://www. ...

  8. Vue $once 函数

    Vue $once 函数 1.作用 $once 是一个函数,可以为 vue 实例绑定一个自定义事件,但是这个事件只会被触发一次,触发之后就会被移除. 类似的监听函数有: $on. 2.使用与 $on ...

  9. Vue.js:vue指令(给标签属性赋Vue变量v-bind,绑定事件 v-on)vue事件处理函数中, 拿到事件对象,6个事件修饰符

    1. 给标签属性赋Vue变量v-bind 在vue中,v-bind指令可以动态的给标签的属性设置值, 语法:v-bind:属性名="vue变量" 简写::属性名="vue ...

最新文章

  1. 牛客华为机试第4题python
  2. shell变量设置与显示
  3. UA PHYS515A 电磁理论II 静电学问题的一个例子
  4. python udp 传输文件_python网络编程:UDP方式传输数据
  5. uva 11269——Setting Problems
  6. 公式中表达单个双引号【】和空值【】的方法及说明
  7. 360的服务器在哪个文件夹,如何卸载服务器上顽固的360
  8. 更改了Composer DSN密码之后View Composer服务启动失败
  9. linux下常用压缩(compress ,gz ,bzip2,xf)命令和打包命令(tar,zip)详解
  10. 案例:多任务udp聊天器
  11. 文字描边加粗_这些PPT描边字,效果好到没朋友~
  12. 突破固化思维,如何快速做好陌生领域数据分析?
  13. 工厂模式概念及其使用场景
  14. 利用Lightroom添加边框及批量导出
  15. PS如何快速简单的给人像美白
  16. 2021.01.05【读书笔记】丨生物信息学与功能基因组学(第四章 局部比对搜索基本工具-BLAST 下)
  17. c语言教学方法措施,C语言教学对策
  18. docker - bridge 网桥
  19. Tecplot中自定义函数
  20. kazam使用_尝试使用2种免费的桌面录制工具:SimpleScreenRecorder和Kazam

热门文章

  1. java 对象数组定义_Java对象数组定义与用法详解
  2. 我关闭了微信朋友圈,把依赖感留给了身边人
  3. HTML标签及浏览器概览
  4. 快慢指针判断链表中是否有环
  5. 高考报志愿选什么专业?
  6. win10关机后自动重启_win10关机后自动开机的解决方法
  7. MFC创建人物行走动画C++
  8. littleVGL学习笔记4——Tasks任务系统
  9. 浏览器通过f12来限制网速
  10. 数据挖掘算法———常用关联算法总结