iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >深入探讨Vue 3中的组合式函数编程方式
  • 722
分享到

深入探讨Vue 3中的组合式函数编程方式

Vue组合式函数Vue组合式编程 2023-05-18 20:05:40 722人浏览 八月长安
摘要

目录什么是组合式函数​鼠标跟踪器示例​异步状态示例​约定和最佳实践​ 命名​输入参数​返回值​副作用​使用限制​通过抽取组合式函数改善代码结构选项式api中使用组合式函数​与其他模式

什么是组合式函数​

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.pageX
  y.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.pageX
    y.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.pageX
    y.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 = null
    error.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: {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->{ mouse.x }}, {<!--{C}%3C!%2D%2D%20%2D%2D%3E-->{ mouse.y }}

副作用​

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

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

使用限制​

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

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

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

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 有三个主要的短板:

  • 不清晰的数据来源:当使用了多个 mixin 时,实例上的数据属性来自哪个 mixin 变得不清晰,这使追溯实现和理解组件行为变得困难。这也是我们推荐在组合式函数中使用 ref + 解构模式的理由:让属性的来源在消费组件时一目了然。
  • 命名空间冲突:多个来自不同作者的 mixin 可能会注册相同的属性名,造成命名冲突。若使用组合式函数,你可以通过在解构变量时对变量进行重命名来避免相同的键名。
  • 隐式的跨 mixin 交流:多个 mixin 需要依赖共享的属性名来进行相互作用,这使得它们隐性地耦合在一起。而一个组合式函数的返回值可以作为另一个组合式函数的参数被传入,像普通函数那样。

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

和无渲染组件的对比​

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

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

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

和React Hooks的对比​

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

到此这篇关于深入探讨Vue 3中的组合式函数编程方式的文章就介绍到这了,更多相关Vue组合式函数内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 深入探讨Vue 3中的组合式函数编程方式

本文链接: https://www.lsjlt.com/news/212414.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 深入探讨Vue 3中的组合式函数编程方式
    目录什么是组合式函数​鼠标跟踪器示例​异步状态示例​约定和最佳实践​ 命名​输入参数​返回值​副作用​使用限制​通过抽取组合式函数改善代码结构选项式API中使用组合式函数​与其他模式...
    99+
    2023-05-18
    Vue组合式函数 Vue组合式编程
  • 深入探讨JavaScript中的async函数
    说白了:await就相当于 then 方法的第一个回调函数,只返回成功的值,失败的值需要 try...catch来捕获。async函数内部抛出错误,会导致返回的 Promise 对象变为reject状态。抛出的错误对象会被catch方法回调...
    99+
    2022-11-22
    async await javascript
  • Lambda 的哲学:Java 函数式编程思想的探讨
    函数式编程思想 函数式编程是一种编程范式,它强调将程序表示为一系列不可变函数的组合。函数式编程的主要特征包括: 不可变性:函数不会改变其输入或内部状态。 函数作为一等公民:函数可以作为参数传递、返回作为结果,甚至可以存储在数据结构中。 ...
    99+
    2024-04-02
  • 深入探讨:Golang 中的数组交集操作
    golang中获取数组交集有三种方法:使用fmt包的sprint和scanf将数组转换为字符串,并查找一个字符串中包含另一个字符串的元素;使用map包创建一个映射,键为另一个数组中的元素...
    99+
    2024-04-04
    golang 数组交集 python
  • Vue函数式组件专篇深入分析
    函数式组件的实现相对容易。一个函数式组件本质上就是一个普通函数,该函数的返回值是虚拟DOM。 在用户接口层面,一个函数式组件就是一个返回虚拟DOM的函数,如下面的代码所示: func...
    99+
    2023-01-05
    Vue函数式组件样式 Vue函数式组件 Vue函数式组件使用场景
  • 深入探讨Golang中的数据处理方法
    深入探讨Golang中的数据处理方法导言:在当今的信息时代,数据是无处不在的。对于开发者来说,处理数据是日常工作中不可或缺的一部分。而Go语言(Golang)作为一种强大且高效的编程语言,提供了许多灵活且易用的数据处理方法。本文将深入探讨G...
    99+
    2023-12-23
    方法 Golang 数据处理
  • 探讨嵌入式设备是否适合使用Go语言编程
    嵌入式设备是否适合使用Go语言编程 随着物联网技术的快速发展,嵌入式设备在我们日常生活中扮演着越来越重要的角色。而在选择合适的编程语言来开发嵌入式设备时,Go语言作为一种编译型的静态类...
    99+
    2024-04-02
  • 深入探讨PHP中rand函数的随机性问题
    标题:深入探讨PHP中rand函数的随机性问题 在编程中,随机数的生成是一项经常会用到的功能之一。在PHP编程中,我们通常会使用rand()函数来生成一个随机数。然而,对于rand()...
    99+
    2024-03-12
    php rand函数 随机性
  • Golang函数式编程深入分析实例
    目录定义集合功能函数实现具体功能函数测试集合功能泛型实现定义集合功能函数 首先定义用于测试的结构体WorkWith: // WorkWith is the struct we'll ...
    99+
    2023-01-10
    Golang函数式编程 Go函数式编程
  • 深入浅出讲解Java8函数式编程
    目录什么是函数式编程Java8内置了一些常用的方法接口FunctionalInterface用的比较多的函数接口总结什么是函数式编程 函数式编程就是一种抽象程度很高的编程范式,纯粹的...
    99+
    2024-04-02
  • 深入探讨Go语言在网络编程中的应用
    Go语言是一种由Google开发的开源编程语言,常被用于网络编程。在当今互联网繁荣的时代,网络编程是至关重要的一个领域,而Go语言在网络编程中得到了广泛应用。本文将深入探讨Go语言在网...
    99+
    2024-04-02
  • 深入探讨:Go语言中goroutine的并发编程之道
    在当前软件开发领域中,对于并发编程的需求越来越迫切。随着硬件技术的发展,多核处理器已经成为主流,而利用并发编程可以充分发挥多核处理器的潜力,提高系统的性能和响应速度。Go语言作为一门并...
    99+
    2024-03-13
    go语言 并发
  • Lambda 流畅如丝:深入浅出 Java 中的函数式编程
    Lambda 表达式是 Java 8 中引入的,它们是对匿名内部类的语法糖,允许更简洁、更流畅地表达函数。Lambda 流将集合元素转换为另一组元素,对集合操作提供了强大的函数式编程功能。 Lambda 表达式的语法 Lambda 表达式...
    99+
    2024-04-02
  • 深入探讨Go语言网络编程中的非NIO技术
    从现在开始,我们要努力学习啦!今天我给大家带来《深入探讨Go语言网络编程中的非NIO技术》,感兴趣的朋友请继续看下去吧!下文中的内容我们主要会涉及到等等知识点,如果在阅读本文过程中有遇到不清楚的地方...
    99+
    2024-04-04
  • JavaScript中的函数式编程函数和组合以及柯里化是怎样的
    JavaScript中的函数式编程函数和组合以及柯里化是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。面向对象编程和函数式编程是两种非常...
    99+
    2024-04-02
  • Java中的函数式编程
    目录1、Lambda2、函数接口2.1 函数描述符3、Java函数接口3.1 Predicate3.2 Consumer 3.3 Function3.4 Supplier3.5 Pr...
    99+
    2024-04-02
  • 深入探讨Go语言中字符串转数组的操作步骤
    Go语言是一种功能强大且易于上手的编程语言,其内置了丰富的标准库和简洁的语法,使得开发者可以快速地实现各种功能。在实际应用中,经常会遇到需要将字符串转换成数组的需求,本文将深入探讨在G...
    99+
    2024-03-12
    字符串 数组 转换 go语言 标准库
  • Python 函数式编程:探索代码的艺术
    纯函数 纯函数不修改外部状态,并且仅依赖于其参数。这意味着它们易于测试和调试,并且可以安全地并行执行。在 Python 中,纯函数可以表示为如下形式: def add(x, y): return x + y 不可变数据 不可变数据...
    99+
    2024-04-02
  • 深入探究一下Java中不同的线程间数据通信方式
    目录1、多线程如何共享数据2、子线程如何继承父线程数据3、相关问题1、多线程如何共享数据 多线程数据共享可以分为以下2种情况,线程实现代码相同及线程实现代码不同。 线程实现代码相同 ...
    99+
    2023-05-16
    Java线程数据通信方式 Java线程数据通信 Java线程通信 Java线程
  • ​​​​​​​Python入门学习之函数式编程的方法
    本篇内容介绍了“Python入门学习之函数式编程的方法”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!前言在 Python 中,函数是「头等公...
    99+
    2023-06-30
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作