用Vue 3 Composition API创建可重用组件的详细指南

330 阅读7分钟

在本教程中,我们将探讨如何使用Vue 3 CompositionAPI及其最新的代码可重用性功能。

代码共享和可重用性是软件开发的基石之一。从最早的编程开始,代码重复的问题就使程序员发明了一些策略来保持他们的代码的干燥、可重用和可移植性。随着时间的推移,这些策略被不断打磨和改进,新的策略也在不断开发。

这同样适用于Vue和其他编程语言和框架。随着Vue框架的发展,它不断提供更好的可重用性方法。

什么是组合API以及为什么要创建它

让我们考虑一下是什么让一段代码可重用。对我来说,可重用性有三个主要原则。

  • 代码的抽象性。当一段代码能够适合多种不同的使用情况时,它就是抽象的(就像许多编程语言中的类)。
  • 代码的可移植性。当一段代码不仅可以在一个项目的不同地方使用,而且可以在不同的项目中使用时,它就是可移植的。
  • 代码解耦(或松耦合)。当改变一个人不需要改变另一个人时,一段代码与另一个人是解耦的。它们尽可能地相互独立。当然,完全解耦是不可能的--这就是为什么开发人员使用的更准确的术语是 "松散耦合"。

Composition API是一种构建和构造Vue 3组件的新策略。它结合了上述的三个原则,并允许创建抽象的、可移植的和松散耦合的组件,这些组件可以在不同的项目之间重复使用和共享。

在框架中添加Vue Composition API的动机

在Vue 3中添加Composition API的动机很明确,也很简单:产生更紧凑、更精简的代码。让我们再来探讨一下这个问题。

当我第一次发现Vue时,我被它的选项(基于对象的)API迷住了。在我看来,与Angular和React的同类产品相比,它更加清晰和优雅。所有东西都有自己的位置,我可以直接把它放在那里。当我有一些数据时,我把它放在data 选项中;当我有一些函数时,我把它们放在methods 选项中,以此类推。

// Options API example
export default {
  props: ['title', 'message'],

  data() {
    return {
      width: 30,
      height: 40
    }
  },

  computed: {
    rectArea() {
      return this.width * this.height
    },
  },

  methods: {
    displayMessage () {
      console.log(`${this.title}: ${this.message}`)
    }
  }
}

所有这些看起来很有秩序,很干净,很容易阅读和理解。然而,事实证明,这只在应用程序相对较小和简单时有效。随着应用程序和它的组件越来越多,代码的碎片化和无序性就会增加。

当Options API被用于大型项目时,代码库很快就开始变得像一个支离破碎的硬盘。一个组件中的不同部分的代码,在逻辑上是属于一起的,但却分布在不同的地方。这使得代码难以阅读、理解和维护。

这就是组合API发挥作用的地方。它提供了一种按顺序组织代码的方法,所有的逻辑部分都作为一个单元被组合在一起。在某种程度上,你可以把Composition API想象成一个磁盘碎片整理工具。它可以帮助你保持代码的紧凑和干净。

这里有一个简化的视觉例子。

Options vs the Composition API

正如你所看到的,用Options API构建的组件代码可能相当零散,而用Composition API构建的组件代码是按功能分组的,看起来更容易阅读和维护。

Vue Composition API的优势

以下是Composition API提供的主要优势的总结。

  • 更好的代码组合。
  • 逻辑上相关的块被保存在一起。
  • 与Vue 2相比,整体性能更好
  • 更干净的代码。代码在逻辑上更加有序,这使得它更有意义,更容易阅读和理解。
  • 易于提取和导入功能。
  • 支持TypeScript,这改善了IDE的整合和代码协助,以及代码调试。(这不是Composition API的特点,但作为Vue 3的一个特点,值得一提。)

合成API基础知识

尽管Composition API具有强大的功能和灵活性,但它还是很简单的。要在一个组件中使用它,我们需要添加一个setup() 函数,实际上这只是在Options API中添加的另一个选项。

export default {
  setup() {
    // Composition API
  }
}

setup() 函数中,我们可以创建反应式变量,以及操作这些变量的函数。然后我们可以返回这些变量和/或函数,我们希望这些变量和/或函数在组件的其他部分可用。为了创建反应式变量,你需要使用Reactivity API函数(ref(),reactive(),computed(), 等等)。要了解更多关于它们的用法,你可以探索这个关于Vue 3 Reacivity系统的综合教程

setup() 函数接受两个参数:propscontext

道具是反应式的,当有新的道具传入时将会被更新。

export default {
  props: ["message"],
  setup(props) {
    console.log(props.message)
  }
}

如果你想对你的道具进行析构,你可以通过在setup() 函数中使用toRefs() 来实现。如果你使用ES6的析构来代替,它将删除道具的反应性。

import { toRefs } from 'vue'

export default {
  props: ["message"],
  setup(props) {
//  const { message } = props   <-- ES6 destructuring. The 'message' is NOT reactive now.
    const { message } = toRefs(props) // Using 'toRefs()' keeps reactivity.
    console.log(message.value)
  }
}

Context是一个普通的JavaScript对象(非反应式),它暴露了其他有用的值,如attrs,slots,emit 。这些相当于选项API中的$attrs,$slots, 和$emit

setup() 函数是在组件实例创建之前执行的。所以你将无法访问以下组件选项。data,computed,methods, 和模板refs。

setup() 函数中,你可以通过使用on 前缀来访问一个组件的生命周期钩。例如,mounted 将变成onMounted 。生命周期函数接受一个回调,当钩子被组件调用时,该回调将被执行。

export default {
  props: ["message"],
  setup(props) {
    onMounted(() => {
      console.log(`Message: ${props.message}`)
    })
  }
}

注意:你不需要明确地调用beforeCreatecreated 钩子,因为setup() 函数本身就能做类似的工作。在setup() 函数中,this 并不是对当前活动实例的引用,因为setup() 是在其他组件选项被解析之前被调用的。

比较选项API和组合API

让我们在Options和Composition API之间做一个快速比较。

首先,这是一个简单的待办事项应用组件,用Options API构建,具有添加和删除任务的功能。

<template>
  <div id="app">
    <h4> {{ name }}'s To Do List </h4>
    <div>
      <input v-model="newItemText" v-on:keyup.enter="addNewTodo" />
      <button v-on:click="addNewTodo">Add</button>
      <button v-on:click="removeTodo">Remove</button>
        <transition-group name="list" tag="ol">
          <li v-for="task in tasks" v-bind:key="task" >{{ task }}</li>
        </transition-group>
    </div>
  </div>
</template>
<script>
  export default {
    data() { 
      return {
        name: "Ivaylo",
        tasks: ["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"],
        newItemText: ""
    }},
    methods: {
      addNewTodo() {
        if (this.newItemText != "") {
          this.tasks.unshift(this.newItemText);
        }
        this.newItemText = "";
      },
      removeTodo() {
        this.tasks.shift();
      },
    }
  }; 
</script> 

为了简洁起见,我在这里省略了CSS代码,因为它并不相关。你可以在Vue 2 Options API的例子中看到完整的代码。

正如你所看到的,这是一个相当简单的例子。我们有三个数据变量和两个方法。让我们看看如何在考虑到Composition API的情况下重写它们。

<script>
  import { ref, readonly } from "vue"

  export default {
    setup () {
      const name = ref("Ivaylo")
      const tasks = ref(["Write my posts", "Go for a walk", "Meet my friends", "Buy fruit"])
      const newItemText = ref("") 

      const addNewTodo = () => {
        if (newItemText.value != "") {
          tasks.value.unshift(newItemText.value);
        }
        newItemText.value = "";
      }
      const removeTodo = () => {
        tasks.value.shift();
      }
      
      return {
        name: readonly(name),
        tasks: readonly(tasks),
        newItemText,
        addNewTodo,
        removeTodo
      }
    }
  }; 
</script> 

正如你在这个Vue 3 Composition API例子中看到的 ,功能是一样的,但所有的数据变量和方法都被移到了一个setup() 函数里面。

为了重新创建三个数据反应式变量,我们使用ref() 函数。然后,我们重新创建addNewTodo()removeTodo() 函数。请注意,所有对this 的使用都被删除了,取而代之的是直接使用变量名,后面是value 属性。因此,我们不写this.newItemText ,而写newItemText.value ,以此类推。最后,我们返回变量和函数,以便它们可以在组件的模板中使用。请注意,当我们在模板中使用它们时,我们不需要使用value 属性,因为所有返回的值都是自动浅层解包的。所以我们不需要改变模板中的任何东西。

我们将nametasks 设为只读,以防止它们在组件之外被改变。在这种情况下,tasks 属性只能由addNewTodo()removeTodo() 来改变。

什么时候组合API适合一个组件,什么时候不适合

某种新技术的产生并不意味着你需要它或必须使用它。在决定是否使用一项新技术之前,你应该考虑一下你是否真的需要它。尽管Composition API提供了一些很大的好处,但在小型和简单的项目中使用它可能会导致不必要的复杂性。这个原理和Vuex的使用是一样的:对于小项目来说,它可能过于复杂。

例如,如果你的组件大多是单一功能的--也就是说,它们只做一件事--你不需要通过使用Composition API来增加不必要的认知负担。但是如果你注意到你的组件越来越复杂,功能越来越多--它们处理不止一个单一的任务和/或它们的功能在你的应用中很多地方都需要--那么你应该考虑使用组合API。在有很多复杂的、多功能的组件的大中型项目中,Composition API将帮助你产生高度可重用和可维护的代码,而没有不必要的黑客或变通。

所以你可以把下面的规则作为一个一般建议。

  • Options API最适合于构建小型、简单、单一功能的组件,这些组件的功能需要较低的可重用性。
  • Composition API最适合于构建更大、更复杂、功能更多的组件,其功能需要更高的可重用性。

继续阅读如何用Vue 3 Composition API创建可重用的组件onSitePoint.