Vue3中的组合式API的详细教程和介绍

文章目录

  • 前言
  • 介绍
  • 组合式 API 基础
    • setup 组件选项
  • 带 ref 的响应式变量
  • 生命周期钩子注册内部 setup
  • watch 响应式更改
  • 独立的 computed 属性
  • 后言

前言

hello world欢迎来到前端的新世界


😜当前文章系列专栏:vue.js
🐱‍👓博主在前端领域还有很多知识和技术需要掌握,正在不断努力填补技术短板。(如果出现错误,感谢大家指出)🌹
💖感谢大家支持!您的观看就是作者创作的动力

介绍

通过创建 Vue 组件,我们可以将接口的可重复部分及其功能提取到可重用的代码段中。仅此一项就可以使我们的应用程序在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要。

假设在我们的应用程序中,我们有一个视图来显示某个用户的仓库列表。除此之外,我们还希望应用搜索和筛选功能。处理此视图的组件可能如下所示:

// src/components/UserRepositories.vue


export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  data () {
    return {
      repositories: [], // 1
      filters: { ... }, // 3
      searchQuery: '' // 2
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
    repositoriesMatchingSearchQuery () { ... }, // 2
  },
  watch: {
    user: 'getUserRepositories' // 1
  },
  methods: {
    getUserRepositories () {
      // 使用 `this.user` 获取用户仓库
    }, // 1
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}

该组件有以下几个职责:

  • 从假定的外部 API 获取该用户名的仓库,并在用户更改时刷新它
  • 使用 searchQuery 字符串搜索存储库
  • 使用 filters 对象筛选仓库

用组件的选项 (datacomputedmethodswatch) 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,尤其是对于那些一开始就没有编写这些组件的人来说。

在这里插入图片描述
一个大型组件的示例,其中逻辑关注点是按颜色分组。

这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。

如果我们能够将与同一个逻辑关注点相关的代码配置在一起会更好。而这正是组合式 API 使我们能够做到的。

组合式 API 基础

既然我们知道了为什么,我们就可以知道怎么做。为了开始使用组合式 API,我们首先需要一个可以实际使用它的地方。在 Vue3 组件中,我们将此位置称为 setup

setup 组件选项

新的 setup 组件选项在创建组件之前执行,一旦 props 被解析,并充当合成 API 的入口点。

WARNING

由于在执行setup时尚未创建组件实例,因此在 setup 选项中没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法。

setup 选项应该是一个接受 propscontext 的函数,我们将在稍后讨论。此外,我们从 setup 返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。

让我们添加 setup 到我们的组件中:

// src/components/UserRepositories.vue


export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup(props) {
    console.log(props) // { user: '' }


    return {} // 这里返回的任何内容都可以用于组件的其余部分
  }
  // 组件的“其余部分”
}

现在让我们从提取第一个逻辑关注点开始 (在原始代码段中标记为“1”)。

从假定的外部 API 获取该用户名的仓库,并在用户更改时刷新它

我们将从最明显的部分开始:

  • 仓库列表
  • 更新仓库列表的函数
  • 返回列表和函数,以便其他组件选项可以访问它们
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'


// 在我们的组件内
setup (props) {
  let repositories = []
  const getUserRepositories = async () => {
    repositories = await fetchUserRepositories(props.user)
  }


  return {
    repositories,
    getUserRepositories // 返回的函数与方法的行为相同
  }
}

这是我们的出发点,但它还不能工作,因为我们的 repositories 变量是非响应式的。这意味着从用户的角度来看,仓库列表将保持为空。我们来解决这个问题!

带 ref 的响应式变量

在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,如下所示:

import { ref } from 'vue'


const counter = ref(0)

ref 接受参数并返回它包装在具有 value property 的对象中,然后可以使用该 property 访问或更改响应式变量的值:

import { ref } from 'vue'


const counter = ref(0)


console.log(counter) // { value: 0 }
console.log(counter.value) // 0


counter.value++
console.log(counter.value) // 1

在对象中包装值似乎不必要,但在 JavaScript 中保持不同数据类型的行为统一是必需的。这是因为在 JavaScript 中,Number 或 String 等基本类型是通过值传递的,而不是通过引用传递的:

在这里插入图片描述
在任何值周围都有一个包装器对象,这样我们就可以在整个应用程序中安全地传递它,而不必担心在某个地方失去它的响应性。

提示

换句话说,ref 对我们的值创建了一个响应式引用。使用引用的概念将在整个组合式 API 中经常使用。

回到我们的例子,让我们创建一个响应式的 repositories 变量:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'


// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }


  return {
    repositories,
    getUserRepositories
  }
}

完成!现在,每当我们调用 getUserRepositories 时,repositories 都将发生变化,视图将更新以反映更改。我们的组件现在应该如下所示:

// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'


export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup (props) {
    const repositories = ref([])
    const getUserRepositories = async () => {
      repositories.value = await fetchUserRepositories(props.user)
    }


    return {
      repositories,
      getUserRepositories
    }
  },
  data () {
    return {
      filters: { ... }, // 3
      searchQuery: '' // 2
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
    repositoriesMatchingSearchQuery () { ... }, // 2
  },
  watch: {
    user: 'getUserRepositories' // 1
  },
  methods: {
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}

我们已经将第一个逻辑关注点中的几个部分移到了 setup 方法中,它们彼此非常接近。剩下的就是在 mounted 钩子中调用 getUserRepositories,并设置一个监听器,以便在 user prop 发生变化时执行此操作。

我们将从生命周期钩子开始。

生命周期钩子注册内部 setup

为了使组合式 API 的特性与选项式 API 相比更加完整,我们还需要一种在 setup 中注册生命周期钩子的方法。这要归功于从 Vue 导出的几个新函数。组合式API上的生命周期钩子与选项式 API 的名称相同,但前缀为 on:即 mounted 看起来像 onMounted

这些函数接受在组件调用钩子时将执行的回调。

让我们将其添加到 setup 函数中:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'


// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }


  onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`


  return {
    repositories,
    getUserRepositories
  }
}

现在我们需要对 user prop 所做的更改做出反应。为此,我们将使用独立的 watch 函数。

watch 响应式更改

就像我们如何使用 watch 选项在组件内的 user property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch 函数执行相同的操作。它接受 3 个参数:

  • 一个响应式引用或我们想要侦听的 getter 函数
  • 一个回调
  • 可选的配置选项

下面让我们快速了解一下它是如何工作的

import { ref, watch } from 'vue'


const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

例如,每当 counter 被修改时 counter.value=5,watch 将触发并执行回调 (第二个参数),在本例中,它将把 'The new counter value is:5' 记录到我们的控制台中。

以下是等效的选项式 API:

export default {
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log('The new counter value is: ' + this.counter)
    }
  }
}

现在我们将其应用到我们的示例中:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'


// 在我们组件中
setup (props) {
  // 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
  const { user } = toRefs(props)


  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `prop.user` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }


  onMounted(getUserRepositories)


  // 在用户 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)


  return {
    repositories,
    getUserRepositories
  }
}

你可能已经注意到在我们的 setup 的顶部使用了 toRefs。这是为了确保我们的侦听器能够对 user prop 所做的更改做出反应。

有了这些变化,我们就把第一个逻辑关注点移到了一个地方。我们现在可以对第二个关注点执行相同的操作——基于 searchQuery 进行过滤,这次是使用计算属性。

独立的 computed 属性

与 ref 和 watch 类似,也可以使用从 Vue 导入的 computed 函数在 Vue 组件外部创建计算属性。让我们回到我们的 counter 例子:

import { ref, computed } from 'vue'


const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)


counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2

在这里,computed 函数返回一个作为 computed 的第一个参数传递的 getter 类回调的输出的一个只读的响应式引用。为了访问新创建的计算变量的 value,我们需要像使用 ref 一样使用 .value property。

让我们将搜索功能移到 setup 中:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'


// in our component
setup (props) {
  // 使用 `toRefs` 创建对 props 的 `user` property 的响应式引用
  const { user } = toRefs(props)


  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `props.user ` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }


  onMounted(getUserRepositories)


  // 在用户 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)


  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })


  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

对于其他的逻辑关注点我们也可以这样做,但是你可能已经在问这个问题了——这不就是把代码移到 setup 选项并使它变得非常大吗?嗯,那是真的。这就是为什么在继续其他任务之前,我们将首先将上述代码提取到一个独立的组合式函数。让我们从创建 useUserRepositories 开始:

// src/composables/useUserRepositories.js


import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'


export default function useUserRepositories(user) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(user.value)
  }


  onMounted(getUserRepositories)
  watch(user, getUserRepositories)


  return {
    repositories,
    getUserRepositories
  }
}

然后是搜索功能:

// src/composables/useRepositoryNameSearch.js


import { ref, computed } from 'vue'


export default function useRepositoryNameSearch(repositories) {
  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(repository => {
      return repository.name.includes(searchQuery.value)
    })
  })


  return {
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

现在在单独的文件中有了这两个功能,我们就可以开始在组件中使用它们了。以下是如何做到这一点:

// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'


export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup (props) {
    const { user } = toRefs(props)


    const { repositories, getUserRepositories } = useUserRepositories(user)


    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)


    return {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      repositories: repositoriesMatchingSearchQuery,
      getUserRepositories,
      searchQuery,
    }
  },
  data () {
    return {
      filters: { ... }, // 3
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
  },
  methods: {
    updateFilters () { ... }, // 3
  }
}

此时,你可能已经知道了这个练习,所以让我们跳到最后,迁移剩余的过滤功能。我们不需要深入了解实现细节,因为这不是本指南的重点。

// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'


export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup(props) {
    const { user } = toRefs(props)


    const { repositories, getUserRepositories } = useUserRepositories(user)


    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)


    const {
      filters,
      updateFilters,
      filteredRepositories
    } = useRepositoryFilters(repositoriesMatchingSearchQuery)


    return {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}

后言

创作不易,要是本文章对广大读者有那么一点点帮助 不妨三连支持一下,您的鼓励就是博主创作的动力

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/208701.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于YOLOv8深度学习的120种犬类检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战、狗类检测、犬种识别

《博主简介》 小伙伴们好,我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源,可关注公-仲-hao:【阿旭算法与机器学习】,共同学习交流~ 👍感谢小伙伴们点赞、关注! 《------往期经典推…

SpringMVC利用@ControllerAdvice和ResponseBodyAdvice接口统一处理返回值

在我们进行Java的Web应用开发时,如何写更少的代码,做更多的事情。如何让开发更容易上手,更专注于业务层面,不需要太关心底层的实现。这里就分享一些我平时在搭建基础框架时候的一些心得体验。 统一处理返回值 在web应用中&#x…

Python爬虫基础之Scrapy框架详解

目录 1. 简介2. Scrapy的安装3. Scrapy的架构4. Scrapy的数据流程5. Scrapy开发流程5.1 创建项目5.2 创建Spider5.3 创建Item5.4 编写Spider5.5 运行Spider 参考文献 原文地址:https://program-park.top/2023/12/01/reptile_5/ 本文章中所有内容仅供学习交流使用&am…

8、内部FLASH模拟EEPROM实验(STM32F407)

STM32编程方式 在线编程(ICP,In-Circuit Programming): 通过JTAG/SWD协议或者系统加载程序(Bootloader)下载用户应用程序到微控制器中。 在程序中编程(IAP,In Application Programming):通过任何一种通信接口(如IO端口,USB,CAN,UART,I2C,SPI等)下载程…

2024 年综合网络安全审计清单

在网络威胁不断演变的时代,确保组织数据和系统的安全至关重要。 全面的网络安全审核清单可以在实现这一目标方面发挥关键作用。但是,什么才是有效的网络安全审核清单呢?以及如何对其进行定制以满足您组织的独特需求? 了解网络安…

【设计模式-2.3】创建型——原型模式

说明:本文介绍设计模式中,创建型中的原型模式; 飞机大战 创建型设计模式关注于对象的创建,原型模式也不例外。如简单工厂和工厂模式中提到过的飞机大战这个例子,游戏中飞机、坦克对象会创建许许多多的实例&#xff0…

水利安全监测方案——基于RTU200的解决方案

引言: 水资源是人类赖以生存的重要基础,对于保障水利系统安全运行以及应对自然灾害起着关键作用。为了实现水利安全监测的目标,我们提出了基于RTU200的解决方案。本方案将结合RTU200的可靠性、灵活性和高效性,为您打造一个全面的…

vue使用实现录音功能js-audio-recorder

前言 最近项目中需要实现一个录音上传功能&#xff0c;用于语音评论可以上录音。 下载插件&#xff1a; npm i js-audio-recorder完整代码 <template><div style"padding: 20px;"><h3>录音上传</h3><div style"font-size:14px"…

[Java学习日记]网络编程

目录 一.常见的软件架构、网络编程三要素、IP 二.利用UDP发送与接收数据 三.改聊天室 四.组播案例 五.TCP通信案例 一.常见的软件架构、网络编程三要素、IP 网络编程&#xff1a;在网络通信协议下&#xff0c;不同的计算机上运行的程序进行的数据传输 在Java中可以使用java…

Promise的resolve和reject方法(手写题)

1.resolve 2.reject 3.手写 1.resolve //构造函数上添加 resolve 方法 Promise.resolve function (value) {return new Promise((resolve, reject) > {if (value instanceof Promise) {value.then((val) > {resolve(val)},(err) > {reject(err)})} else {resolve(v…

springboot项目中注入bean后,调用时报n

需求&#xff1a; 在socket接收到上报数据后&#xff0c;在handler中调用工具类中ProtocolAnalyse的conAnalyse(byte[] data, int dataLen)解析数据。解析数据后&#xff0c;将解析后的结果保存至数据库。注入了三个bean&#xff1a; Autowiredprivate PersonTeService person…

基于融合正余弦和柯西变异的麻雀优化算法(SCSSA)-CNN-BiLSTM(双向长短期记忆网络)的时间序列预测模型

融合正余弦和柯西变异的麻雀优化算法&#xff08;SCSSA&#xff09;原理如下&#xff1a; ①采用折射反向学习策略初始化麻雀算法个体&#xff0c;基本思想是通过计算当前解的反向解来扩大搜索范围&#xff0c;借此找出给定问题更好的备选解&#xff1b;②采用正余弦策略替换原…

2023年建筑轮廓高度数据技术服务

在之前的文章【数据分享】全国62个城市建筑轮廓数据带层数&#xff08;2018年&#xff09;和【数据分享】全国77个城市建筑轮廓数据带高度&#xff08;2019年&#xff09;里我们分别分享了2018年和2019年2020年2021年2022年的建筑轮廓数据&#xff0c;数据年份也有些老了&#…

react实现加载动画

1.Spinning.tsx import "./Spinning.scss";interface Props {isLoading: boolean;children?: React.ReactNode; }const Spinning: React.FC<Props> ({isLoading true,children }) > {return <div className{spinning-wrapper${isLoading ? " l…

Hdoop学习笔记(HDP)-Part.2 核心组件原理

二、核心组件原理 1.分布式协调ZooKeeper (1)应用场景 使用分布式系统就无法避免对节点管理的问题&#xff08;需要实时感知节点的状态、对节点进行统一管理等&#xff09;&#xff0c;而由于这些问题处理起来可能相对麻烦和提高了系统的复杂性&#xff0c;ZooKeeper作为一个…

5.C转python

新始: 13.列表可被改变(数据),元组不可被改变(数据),二者皆与C中的数组的大致相同 14.创建列表方法: 1.一个[ ]就是一个空的列表 2.使用list函数来创建列表 如: 15.可以在[ ]内部指定列表的初始值,打印方法: 如: 16.在python中,在同一个列表中,可以放不同类型的变量(也可…

【零基础入门Docker】Dockerfile中的USER指令以及dockerfile命令详解

✍面向读者&#xff1a;所有人 ✍所属专栏&#xff1a;Docker零基础入门专栏 目录 第 1 步&#xff1a;创建 Dockerfile 第 2 步&#xff1a;构建 Docker 镜像 第 3 步&#xff1a;运行 Docker 容器 第 4 步&#xff1a;验证输出 dockerfile命令详解 最佳实践 默认情况下…

oops-framework框架 之 初始了解(一)

引擎&#xff1a;CocosCreator 环境&#xff1a; Mac Gitee: oops-framework 简介 oops-framework是由作者dgflash编写&#xff0c;基于CocosCreator 3.x而实现的开源框架。 该框架以插件形式存在&#xff0c;主要目的是为了降低与项目的耦合&#xff0c;并且通过插件内部的…

【LeetCode:1657. 确定两个字符串是否接近 | 计数 + 阅读理解】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

若依前后端分离版idea启动

1.项目地址 https://gitee.com/y_project/RuoYi-Vue2.修改配置 修改数据库配置 3.执行sql 4.启动项目 启动成功 5.启动前端 首先要安装nodejs,版本不能太高,本人用的nodejs11版本的 npm install --registry https://registry.npm.taobao.orgnpm run dev启动成功 访问 h…