Vue 3.0 组合式API 介绍 【Vue3 从零开始】

news2024/12/25 22:39:50

提示

在阅读文档之前,你应该已经熟悉了这两个 Vue 基础和创建组件。

在 Vue Mastery 上观看关于组合式 API 的免费视频。

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

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

 
  1. // src/components/UserRepositories.vue
  2. export default {
  3. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  4. props: {
  5. user: { type: String }
  6. },
  7. data () {
  8. return {
  9. repositories: [], // 1
  10. filters: { ... }, // 3
  11. searchQuery: '' // 2
  12. }
  13. },
  14. computed: {
  15. filteredRepositories () { ... }, // 3
  16. repositoriesMatchingSearchQuery () { ... }, // 2
  17. },
  18. watch: {
  19. user: 'getUserRepositories' // 1
  20. },
  21. methods: {
  22. getUserRepositories () {
  23. // 使用 `this.user` 获取用户仓库
  24. }, // 1
  25. updateFilters () { ... }, // 3
  26. },
  27. mounted () {
  28. this.getUserRepositories() // 1
  29. }
  30. }

该组件有以下几个职责:

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

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

一个大型组件的示例,其中逻辑关注点是按颜色分组。

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

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

#组合式 API 基础

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

#setup 组件选项

观看 Vue Mastery 上的免费 setup 视频。

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

WARNING

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

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

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

 
  1. // src/components/UserRepositories.vue
  2. export default {
  3. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  4. props: {
  5. user: { type: String }
  6. },
  7. setup(props) {
  8. console.log(props) // { user: '' }
  9. return {} // 这里返回的任何内容都可以用于组件的其余部分
  10. }
  11. // 组件的“其余部分”
  12. }

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

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

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

  • 仓库列表
  • 更新仓库列表的函数
  • 返回列表和函数,以便其他组件选项可以访问它们

 
  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. // 在我们的组件内
  4. setup (props) {
  5. let repositories = []
  6. const getUserRepositories = async () => {
  7. repositories = await fetchUserRepositories(props.user)
  8. }
  9. return {
  10. repositories,
  11. getUserRepositories // 返回的函数与方法的行为相同
  12. }
  13. }

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

#带 ref 的响应式变量

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

 
  1. import { ref } from 'vue'
  2. const counter = ref(0)

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

 
  1. import { ref } from 'vue'
  2. const counter = ref(0)
  3. console.log(counter) // { value: 0 }
  4. console.log(counter.value) // 0
  5. counter.value++
  6. console.log(counter.value) // 1

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

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

提示

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

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

 
  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref } from 'vue'
  4. // in our component
  5. setup (props) {
  6. const repositories = ref([])
  7. const getUserRepositories = async () => {
  8. repositories.value = await fetchUserRepositories(props.user)
  9. }
  10. return {
  11. repositories,
  12. getUserRepositories
  13. }
  14. }

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

 
  1. // src/components/UserRepositories.vue
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref } from 'vue'
  4. export default {
  5. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  6. props: {
  7. user: { type: String }
  8. },
  9. setup (props) {
  10. const repositories = ref([])
  11. const getUserRepositories = async () => {
  12. repositories.value = await fetchUserRepositories(props.user)
  13. }
  14. return {
  15. repositories,
  16. getUserRepositories
  17. }
  18. },
  19. data () {
  20. return {
  21. filters: { ... }, // 3
  22. searchQuery: '' // 2
  23. }
  24. },
  25. computed: {
  26. filteredRepositories () { ... }, // 3
  27. repositoriesMatchingSearchQuery () { ... }, // 2
  28. },
  29. watch: {
  30. user: 'getUserRepositories' // 1
  31. },
  32. methods: {
  33. updateFilters () { ... }, // 3
  34. },
  35. mounted () {
  36. this.getUserRepositories() // 1
  37. }
  38. }

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

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

#生命周期钩子注册内部 setup

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

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

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

 
  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted } from 'vue'
  4. // in our component
  5. setup (props) {
  6. const repositories = ref([])
  7. const getUserRepositories = async () => {
  8. repositories.value = await fetchUserRepositories(props.user)
  9. }
  10. onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`
  11. return {
  12. repositories,
  13. getUserRepositories
  14. }
  15. }

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

#watch 响应式更改

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

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

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

 
  1. import { ref, watch } from 'vue'
  2. const counter = ref(0)
  3. watch(counter, (newValue, oldValue) => {
  4. console.log('The new counter value is: ' + counter.value)
  5. })

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

以下是等效的选项式 API:

 
  1. export default {
  2. data() {
  3. return {
  4. counter: 0
  5. }
  6. },
  7. watch: {
  8. counter(newValue, oldValue) {
  9. console.log('The new counter value is: ' + this.counter)
  10. }
  11. }
  12. }

有关 watch 的详细信息,请参阅我们的深入指南。

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

 
  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted, watch, toRefs } from 'vue'
  4. // 在我们组件中
  5. setup (props) {
  6. // 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
  7. const { user } = toRefs(props)
  8. const repositories = ref([])
  9. const getUserRepositories = async () => {
  10. // 更新 `prop.user` 到 `user.value` 访问引用值
  11. repositories.value = await fetchUserRepositories(user.value)
  12. }
  13. onMounted(getUserRepositories)
  14. // 在用户 prop 的响应式引用上设置一个侦听器
  15. watch(user, getUserRepositories)
  16. return {
  17. repositories,
  18. getUserRepositories
  19. }
  20. }

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

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

#独立的 computed 属性

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

 
  1. import { ref, computed } from 'vue'
  2. const counter = ref(0)
  3. const twiceTheCounter = computed(() => counter.value * 2)
  4. counter.value++
  5. console.log(counter.value) // 1
  6. console.log(twiceTheCounter.value) // 2

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

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

 
  1. // src/components/UserRepositories.vue `setup` function
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted, watch, toRefs, computed } from 'vue'
  4. // in our component
  5. setup (props) {
  6. // 使用 `toRefs` 创建对 props 的 `user` property 的响应式引用
  7. const { user } = toRefs(props)
  8. const repositories = ref([])
  9. const getUserRepositories = async () => {
  10. // 更新 `props.user ` 到 `user.value` 访问引用值
  11. repositories.value = await fetchUserRepositories(user.value)
  12. }
  13. onMounted(getUserRepositories)
  14. // 在用户 prop 的响应式引用上设置一个侦听器
  15. watch(user, getUserRepositories)
  16. const searchQuery = ref('')
  17. const repositoriesMatchingSearchQuery = computed(() => {
  18. return repositories.value.filter(
  19. repository => repository.name.includes(searchQuery.value)
  20. )
  21. })
  22. return {
  23. repositories,
  24. getUserRepositories,
  25. searchQuery,
  26. repositoriesMatchingSearchQuery
  27. }
  28. }

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

 
  1. // src/composables/useUserRepositories.js
  2. import { fetchUserRepositories } from '@/api/repositories'
  3. import { ref, onMounted, watch } from 'vue'
  4. export default function useUserRepositories(user) {
  5. const repositories = ref([])
  6. const getUserRepositories = async () => {
  7. repositories.value = await fetchUserRepositories(user.value)
  8. }
  9. onMounted(getUserRepositories)
  10. watch(user, getUserRepositories)
  11. return {
  12. repositories,
  13. getUserRepositories
  14. }
  15. }

然后是搜索功能:

 
  1. // src/composables/useRepositoryNameSearch.js
  2. import { ref, computed } from 'vue'
  3. export default function useRepositoryNameSearch(repositories) {
  4. const searchQuery = ref('')
  5. const repositoriesMatchingSearchQuery = computed(() => {
  6. return repositories.value.filter(repository => {
  7. return repository.name.includes(searchQuery.value)
  8. })
  9. })
  10. return {
  11. searchQuery,
  12. repositoriesMatchingSearchQuery
  13. }
  14. }

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

 
  1. // src/components/UserRepositories.vue
  2. import useUserRepositories from '@/composables/useUserRepositories'
  3. import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
  4. import { toRefs } from 'vue'
  5. export default {
  6. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  7. props: {
  8. user: { type: String }
  9. },
  10. setup (props) {
  11. const { user } = toRefs(props)
  12. const { repositories, getUserRepositories } = useUserRepositories(user)
  13. const {
  14. searchQuery,
  15. repositoriesMatchingSearchQuery
  16. } = useRepositoryNameSearch(repositories)
  17. return {
  18. // 因为我们并不关心未经过滤的仓库
  19. // 我们可以在 `repositories` 名称下暴露过滤后的结果
  20. repositories: repositoriesMatchingSearchQuery,
  21. getUserRepositories,
  22. searchQuery,
  23. }
  24. },
  25. data () {
  26. return {
  27. filters: { ... }, // 3
  28. }
  29. },
  30. computed: {
  31. filteredRepositories () { ... }, // 3
  32. },
  33. methods: {
  34. updateFilters () { ... }, // 3
  35. }
  36. }

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

 
  1. // src/components/UserRepositories.vue
  2. import { toRefs } from 'vue'
  3. import useUserRepositories from '@/composables/useUserRepositories'
  4. import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
  5. import useRepositoryFilters from '@/composables/useRepositoryFilters'
  6. export default {
  7. components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  8. props: {
  9. user: { type: String }
  10. },
  11. setup(props) {
  12. const { user } = toRefs(props)
  13. const { repositories, getUserRepositories } = useUserRepositories(user)
  14. const {
  15. searchQuery,
  16. repositoriesMatchingSearchQuery
  17. } = useRepositoryNameSearch(repositories)
  18. const {
  19. filters,
  20. updateFilters,
  21. filteredRepositories
  22. } = useRepositoryFilters(repositoriesMatchingSearchQuery)
  23. return {
  24. // 因为我们并不关心未经过滤的仓库
  25. // 我们可以在 `repositories` 名称下暴露过滤后的结果
  26. repositories: filteredRepositories,
  27. getUserRepositories,
  28. searchQuery,
  29. filters,
  30. updateFilters
  31. }
  32. }
  33. }

我们完成了!

请记住,我们只触及了组合式 API 的表面以及它允许我们做什么。要了解更多信息,请参阅深入指南。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SAP ERP系统PP模块计划策略2050详解

SAP/ERP系统中面向订单生产的计划策略主要有20和50两个策略,这两个策略都是面向订单生产的计划策略,也是离散制造行业应用比较广泛的策略。它们之间最大差异就是在于20策略完全是由订单驱动,而50策略是预测加订单驱动,本文主要介绍…

【Leetcode 剑指Offer】第 14 天 搜索与回溯算法(中等)

文章目录剑指 Offer 12. 矩阵中的路径DFS剪枝面试题13. 机器人的运动范围剑指 Offer 12. 矩阵中的路径 典型矩阵搜索题 DFS剪枝 深度优先搜索: 可以理解为暴力法遍历矩阵中所有字符串可能性。DFS 通过递归,先朝一个方向搜到底,再回溯至上…

实现迭代回声服务器端/客户端

文章目录1.迭代服务器端/客户端2.迭代回声服务器端/客户端2.1 echo_server.c2.2 echo_client.c3.回声客户端存在的问题4.回声客户端问题解决方法1.迭代服务器端/客户端 之前讨论的 HelloWorld 服务器端处理完 111 个客户端连接请求即退出,连接请求等待队列实际没有…

唯品会盈利十年:韧性有余,冲劲不足

配图来自Canva可画唯品会在电商市场是一个特殊的存在,从2012年第四季度首次盈利至今,唯品会已经连续十年保持盈利。这十年,电商产业逐渐走向成熟,电商玩家新老接替成层出不穷,曾经家喻户晓的蘑菇街、聚美优品、苏宁易购…

Vue2.0开发之——购物车案例-Goods组件封装-商品数量的加减及总数量(53)

一 概述 Goods点击加减实现修改数量的原理Goods点击增加实现实例Goods点击-减少实现实例Footer计算商品总数量 二 Goods点击加减实现修改数量的原理 点击Counter组件里面的加减,修改Counter组件里面的数量Counter组件的数量变化时,Goods商品的数量相应…

leetcode-每日一题-807(中等,数组)

正常情况第一眼看这道题,看懂意思的话很简单就可以解出来。给你一座由 n x n 个街区组成的城市,每个街区都包含一座立方体建筑。给你一个下标从 0 开始的 n x n 整数矩阵 grid ,其中 grid[r][c] 表示坐落于 r 行 c 列的建筑物的 高度 。城市的…

C++各类设计模式及实现详解

软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。设计模式中运用了面向对象编程语言的重要特性:封装、继承、多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累。最近看设计模式的书&#xff0…

Jetpack Compose 中的重组作用域和性能优化

只有读取可变状态的作用域才会被重组 这句话的意思是只有读取 mutableStateOf() 函数生成的状态值的那些 Composable 函数才会被重新执行。注意,这与 mutableStateOf() 函数在什么位置被定义没有关系。读取操作指的是对状态值的 get 操作。也就是取值的操作。 从一…

Rasa 3.x 学习系列-Rasa [3.4.4] - 2023-02-17新版本发布

Rasa 3.x 学习系列-Rasa [3.4.4] - 2023-02-17新版本发布 任何人都可以学习Rasa之优秀Rasa学习资源推荐 欢迎同学们报名Gavin老师的Rasa系列课程,任何人都可以学习Rasa之优秀Rasa学习资源推荐: 1.NLP on Transformers高手之路137课 2 .Rasa 3.X 智能对话机器人案例开发硬核…

Linux如何性能优化,怎么理解平均负载?如何利用系统缓存优化程序的运行效率?内存泄漏如何定位和处理?如何“快准狠”找到系统内存存在的问题?

Linux如何性能优化,怎么理解平均负载?如何利用系统缓存优化程序的运行效率?内存泄漏如何定位和处理?如何“快准狠”找到系统内存存在的问题?1. 性能优化1.1 性能指标1.2 到底应该怎么理解"平均负载"1.3 平均…

插件系列 vue2安装tailwindcss

先说结论,tailwindcss在vue2中引入,可以与其它css预处理混用,并不冲突, vue3可以直接参考官网的安装方式即可。 官方网址:https://www.tailwindcss.cn/ 安装步骤: 直接安装 创建文件 tailwindcss.css main.js全局引…

分析了近500位产品经理后,得出这些产品经理的日常

前些天,我从实现网上抓取了497位产品经理的个人信息,这些产品人平均工作时间超过5年,来自200多家公司,其中152人拥有BAT 、京东、美团、网易、小米等一线互联网公司工作经历,当然也有很大一部分来自不知名的创业公司。 以下是正文: 对这里面各产品经理擅长技

HBase单机版安装详细步骤

目录 一、安装HBase前置环境 1.jdk 2.hadoop 3.zookeeper 二、安装HBase步骤 (一)解压 (二)修改文件名 (三)修改配置文件 (四)配置HBase的环境变量 (五)启动HBase 1.启动hadoop 2.启动zookeeper 3.启动hbase 4.jps出现HMaster和HRegionServer 5.HBase的WebUI (…

Docker那点事

Docker引擎是用于运行和编排容器的基础设施工具。有VMware管理经验的可以将其类比为ESXi。ESXi是运行虚拟机的核心管理程序,而Docker引擎是运行容器的核心容器运行时。 其他Docker公司或第三方的产品都是围绕Docker引擎进行开发和集成的。如图 所示,Docker引擎位于中心,其…

100种思维模型之升维思维模型-026

爱因斯坦曾说:这个层次的问题,很难靠这个层次的思考来解决。 如,你很穷,然后紧衣缩食,结果却依然入不敷出;你很胖,然后拼命节食,结果却依然大腹便便;你很忙,然…

海洋风场数据

本篇文章主要介绍了三种海洋风场数据集基本信息(从官网中提取我认为比较重要的信息),以及如何下载(下载中也遇到很多问题,有的问题现在也存在,大家集思广益,看看有没有好的方法!&…

CSS - 选择器详解 - 子代、后代选择器详解 - 伪类选择器 - 测试

目录测试准备:子代选择器 >测试代码:指定id的子代选择器后代选择器 (以空格隔开)指定 id 的后代选择器指定 class 类 的后代选择器多空格后代选择器详解 (特别重要)伪类选择器 :参考链接:测试准备: 新建一个测试项目文件夹 te…

WebSocket与Socket、TCP、HTTP的关系

目录:1、名词解析;2、WebSocket简介与原理;3、WebSocket和Http的关系和异同点;4、WebSocket与Socket的区别;5、Socket和TCP/IP;6、一个应用程序的通信链路;1、基础名词解析:&#xf…

十三、Spring对事务的支持

1 事务概述 什么是事务 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。多条DML要么同时成功&#xf…

Antlr4:使用grun命令,触发NoClassDefFoundError

1. 意外的发现 在学习使用grun命令时,从未遇到过错误 最近使用grun命令,却遇到了NoClassDefFoundError的错误,使得grun测试工具无法成功启动 错误复现: 使用antlr4命令编译Hello.g4文件,并为指定package(…