如何使用Vue 3 Composition API创建可重用组件?

2021年11月30日01:26:49 发表评论 1,037 次浏览

在本教程中,我们将探索如何使用 Vue 3 Composition API及其最新的代码重用能力。

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

如何使用Composition API创建可重用组件?这同样适用于Vue以及其他编程语言和框架。随着 Vue 框架的发展,它继续提供更好的可重用性方法。

什么是 Composition API 及其创建原因

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

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

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

将 Vue Composition API 添加到框架的动机

将 Composition API 添加到 Vue 3 的动机很简单:生成更紧凑和碎片整理的代码。让我们进一步探讨一下。

Vue 3 Composition API创建可重用组件实例:当我第一次发现 Vue 时,我被它的 Options(基于对象的)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 时,代码库很快开始变得像一个碎片化的硬盘。组件中代码的不同部分,逻辑上属于一起,分布在不同的地方。这使得代码难以阅读、理解和维护。

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

这是一个简化的视觉示例:

如何使用Vue 3 Composition API创建可重用组件?

如你所见,使用 Options API 构建的组件代码可能非常分散,而使用 Composition API 构建的组件代码按功能分组,看起来更易于阅读和维护。

Vue Composition API 优势

以下是 Composition API 提供的主要优势的摘要:

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

Composition API 基础

如何使用Composition API创建可重用组件?尽管 Composition API 功能强大且灵活,但它非常简单。要在组件中使用它,我们需要添加一个setup()函数,这实际上只是添加到 Options API 的另一个选项:

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

setup()函数内部,我们可以创建反应变量和操作它们的函数。然后我们可以返回那些我们希望在组件的其余部分可用的变量和/或函数。要创建反应性变量,你需要使用反应性 API函数(ref()reactive()computed()等)。要了解有关其用法的更多信息,你可以浏览有关 Vue 3 Reacivity 系统的综合教程。

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

道具是反应式的,并且会在传入新道具时更新:

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

如果你想解构你的道具,你可以通过toRefs()setup()函数内部使用来做到这一点。如果你改用 ES6 解构,它将移除props 反应性:

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 对象(非响应式),它公开其他有用的值,如attrsslotsemit。这些等效于$attrs$slots$emit来自选项 API。

setup()函数在组件实例创建之前执行。所以,你不会有机会获得以下组件的选项:datacomputedmethods,和模板裁判。

Vue 3 Composition API如何创建可重用组件?在该setup()函数中,你可以使用on前缀访问组件的生命周期钩子。例如,mounted将成为onMounted。生命周期函数接受一个回调,该回调将在组件调用钩子时执行:

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

注意:你不需要显式调用beforeCreatecreated钩子,因为该setup()函数本身完成了类似的工作。在setup()函数中,this不是对当前活动实例的引用,因为setup()在解析其他组件选项之前调用。

比较 Options API 和 Composition 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()

什么时候 Composition API 适合组件,什么时候不适合

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

例如,如果你的组件大多是单一功能的——也就是说,它们只做一件事——你不需要通过使用 Composition API 添加不必要的认知负载。但是,如果你注意到你的组件变得复杂且功能多样——它们处理多个单一任务和/或应用程序中的许多地方都需要它们的功能——那么你应该考虑使用 Composition API。在具有大量复杂、多功能组件的大中型项目中,Composition API 将帮助你生成高度可重用和可维护的代码,而无需进行不必要的黑客攻击或变通方法。

因此,你可以将以下规则作为一般建议:

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

什么是 Vue 可组合?

Composition API的秘密武器是能够创建称为composables 的高度可重用模块。它们允许我们提取反应状态和功能,并在其他组件中重用它。Composables 相当于 Options API 中的 mixin。它们也可以被认为是 React hooks的等价物。

在可组合之前,有三种方法可以在组件之间重用和共享代码:实用程序函数、mixin 和无渲染组件。但是可组合物击败了它们。让我们看看为什么。

实用功能

实用函数很有用,但也很有限,因为它们无法处理 Vue 特定的特性,比如响应式状态。下面是一个例子:

// utils.js 
export function increment(count) {
  return count++;
}
...

在这里,我们有一个increment(count)实用函数,它将计数变量递增 1。但是我们不能在这里定义反应状态。我们需要count在消费组件中添加一个反应变量,如下所示:

// Counter.vue
<template>
  <p>{{ count }}</p>
  <button v-on:click="increment(count)">Increment</button>
</template>

import { increment } from './utils.js'

export default {
  data() {
    return { count: 0 }
  }
}

无渲染组件

Vue 3 Composition API创建可重用组件实例:无渲染组件(不渲染任何 HTML 模板,只渲染状态和功能的组件)比实用函数好一点,因为它们可以处理 Vue 特定的特性,但它们的灵活性也有限。下面是一个例子:

// RenderlessCounter.vue
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  render() {
    return this.$slots.default({
      count: this.count,
      increment: this.increment
  });
}

Vue 3 Composition API如何创建可重用组件?这里好一点,因为我们可以定义反应状态并在作用域插槽的帮助下导出它。当我们实现组件时,我们使用定义的变量和方法来构建自定义模板:countincrement()

// Counter.vue
<renderless-counter>
  <template v-slot:default="{count, increment}">
    <p>{{ count }}</p>
    <button v-on:click="increment">Increment</button>
  </template>
</renderless-counter>

Mixin

Mixin 是在使用 Options API 构建的组件之间共享代码的官方方式。mixin 只是一个导出的选项对象:

// CounterMixin.js
export default {
  data() {
    return { count: 0 }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}

我们可以导入 mixin 的 options 对象并使用它,就好像它的成员属于消费组件的 options 对象一样:

// Counter.vue
<template>
  <p>{{ count }}</p>
  <button v-on:click="increment">Increment</button>
</template>

import CounterMixin from './CounterMixin'

export default {
  mixins: [CounterMixin]
}

如果组件已经定义了一些选项(datamethodscomputed等),它们将与来自导入的 mixin 的选项合并。我们很快就会看到,这种行为有一些严重的缺点。

与可组合物相比,mixin 有一些严重的缺点:

  • 数据源被遮挡。当一个组件的数据来自多个 mixin 时,我们无法确定哪些属性来自哪个 mixin。使用全局注册的 mixin 时也是如此。
  • 限制重用性。Mixin 不接受参数,所以我们不能添加额外的逻辑。
  • 名称冲突。如果两个或多个 mixin 具有相同名称的属性,则将使用最后一个 mixin 的属性,这可能不是我们想要的。
  • 没有数据保护。我们不能确定消费组件不会更改 mixin 的属性。

Vue 可组合的好处

作为本节的总结,让我们总结一下 Vue 3 可组合的主要好处:

  • 数据源是透明的。要使用可组合项,我们需要导入它们并使用解构来提取所需的数据。所以我们可以清楚地看到每个属性/方法的来源。
  • 没有名称冲突。我们可以通过重命名来使用来自多个可组合的具有相同名称的属性。
  • 数据受到保护。我们可以将返回的属性设为只读,从而限制来自其他组件的变异。原理与 Vuex 中的突变相同。
  • 共享状态。通常,组件中使用的每个可组合项都会创建一个新的本地状态。但是我们也可以定义全局状态,以便在不同组件中使用可组合项时,它们将共享相同的状态。

创建和使用 Vue Composable

如何使用Composition API创建可重用组件?在本节中,我们将学习如何创建和使用自定义 Vue 3 可组合项。

注意:对于这个项目,你需要在你的机器上安装Node和Vue CLI

让我们使用 Vue CLI创建一个新的 Vue 3 项目

vue create vue-composition-api-examples

当系统要求你选择预设时,请确保你选择了默认的 Vue 3 选项。

你可以在Vue Composition API 示例存储库中找到所有项目文件。

创建一个数据获取可组合

在下面的示例中,我们将创建一个可用于各种扫描仪的自定义数据获取组合。

首先,创建一个src/composables文件夹并向其中添加一个useFetch.js文件。这是该文件的代码:

import {toRefs, ref, reactive} from 'vue';

export function useFetch(url, options) {
  const data = ref(null);
  const state = reactive({
    error: null,
    loading: false
  });

  const fetchData = async () => {
    state.loading = true;
    try {
      const res = await fetch(url, options);
      data.value = await res.json();
    } catch (e) {
      state.error = e;
    } finally {
      state.loading = false;
    }
  };

  fetchData();
  
  return {data, ...toRefs(state)};
}

从技术上讲,可组合只是我们导出的一个函数(useFetch()在我们的例子中)。在那个函数中,我们创建datastate变量。然后我们创建一个fetchData()函数,在该函数中我们使用Fetch API从特定来源获取数据并将结果分配给data属性。在该fetchData()函数之后,我们立即调用它以将获取的数据分配给变量。最后,我们返回所有变量。我们toRefs()在这里使用来正确提取errorloading变量,使它们保持反应性。

伟大的!现在,让我们看看如何在组件中使用我们的可组合。

Vue 3 Composition API创建可重用组件实例:在src/components文件夹中,添加一个UserList.vue文件,内容如下:

<template>
  <div v-if="error">
    <h2>Error: {{ error }}</h2>
  </div>
  <div v-if="loading">
    <h2>Loading data...</h2>
  </div>
  <h2>Users</h2>
  <ul v-for="item in data" :key="item.id">
    <li><b>Name:</b> {{ item.name }} </li>
    <li><b>Username:</b> {{ item.username}} </li>
  </ul>
</template>

<script>
import { useFetch } from '../composables/useFetch.js';

export default {
  setup() {
    const {data, error, loading} = useFetch(
      'https://jsonplaceholder.typicode.com/users',
      {}
    );
   
    return {
      data,
      error,
      loading
    };
  }
};
</script> 

<style scoped>
  ul {
    list-style-type: none;
  }
</style>

在这里,我们导入useFetch()可组合项,然后在setup()函数内提取其变量。返回变量后,我们可以在模板中使用它们来创建用户列表。在模板中,我们使用的v-if指令来检查的感实性errorloading,如果其中一个为真,则显示相应的消息。然后,我们使用v-for指令和data属性来创建用户的实际列表。

我们需要做的最后一件事是在App.vue文件中添加组件。打开App.vue文件并将其内容替换为以下内容:

<template>
  <div id="app">
    <user-list />
  </div>
</template>

<script>
import UserList from "./components/UserList";

export default {
  name: "App",
  components: {
    UserList
  }
};
</script>

就是这样。这是创建和使用可组合的基础。但是让我们更进一步,让用户列表组件更加灵活和可重用。

创建高度可重用的组件

Vue 3 Composition API如何创建可重用组件?重命名UserList.vueUniversalList.vue并替换其具有以下内容:

<template>
  <div v-if="error">
    <h2>Error: {{ error }}</h2>
  </div>
  <div v-if="loading">
    <h2>Loading data...</h2>
  </div>
  <slot :data="data"></slot>
</template>

<script>
import { useFetch } from '../composables/useFetch.js';

export default {
  props: ['url'],
  setup(props) {
    const {data, error, loading} = useFetch(
      props.url,
      {}
    );
   
    return {
      data,
      error,
      loading
    };
  }
};
</script> 

这里有两个重要的变化。首先,当我们调用 时useFetch(),不是显式添加 URL,而是用urlprop替换它。这样,我们可以根据需要使用不同的 URL。其次,我们添加了一个插槽组件并提供了data作为它的道具,而不是列表的预制模板。这样,我们就可以在实现组件时使用我们需要的任何模板。让我们看看如何在实践中做到这一点。

将 的内容替换App.vue为以下内容:

<template>
  <div id="app">
    <universal-list url="https://jsonplaceholder.typicode.com/todos" v-slot="{ data }">
      <h2>Todos</h2>
      <ol>
        <li v-for="item in data" :key="item.id"> {{ item.title }} - {{ item.completed }} </li>
      </ol>
    </universal-list>
  </div>
</template>

<script>
import UniversalList from "./components/UniversalList";

export default {
  name: "App",
  components: {
    UniversalList
  }
};
</script>

现在,当我们包含通用列表组件时,我们可以根据需要提供自定义模板。我们添加所需的 URL 并使用v-slot指令从useFetch()可组合中获取数据。最后,我们按照自己的意愿构造获取的数据。在我们的例子中,它是一个待办事项列表。

为了清楚起见,这些示例已被简化,但它们有效地展示了创建和使用可组合项以及构建可重用组件的主要原则。一旦掌握了基础知识,就可以继续学习有关组件可重用性的其他小技巧和窍门,并不断改进你现在和/或以前构建的内容。

结论

如何使用Composition API创建可重用组件?在计划和讨论 Composition API 时,许多人认为这是错误的方法。幸运的是,许多其他人看到了这种功能的潜力。我希望本教程也能帮助你看到它。Composables 解决了 mixin 和实用函数的许多问题,并提供了一种很好的方法来使我们的代码更可重用、更紧凑和更干净。对我来说,Composition API 与 Reactivity API 和插槽相结合,形成了可重用性的神圣三位一体。😊

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: