设计模式
可组合组件
选项 API
在 Vue 引入 Composition API 之前,开发人员依靠 **选项 API** 来组织组件逻辑,其中包括响应式数据、生命周期方法、计算属性等。选项 API 允许在特定选项中定义这些方面,如下面的示例所示
<!-- Template -->
<script>
export default {
name: "MyComponent",
props: {
// props
},
data() {
// data
},
computed: {
// computed properties
},
watch: {
// properties to watch
},
methods: {
// methods
},
created() {
// lifecyle methods like created
},
// ...
};
</script>
<!-- Styles -->
虽然这种方法已经达到了它的目的,并且在 Vue v3 中仍然适用,但随着组件越来越大、越来越复杂,它会变得难以管理和维护。在特定选项中定义组件逻辑会使代码更难阅读和理解,尤其是在处理大型组件时。在这个设置中,提取和重用组件之间的通用逻辑也可能很困难。
让我们看一个简单的 App
组件示例,它渲染两个单独的子组件——Count
和 Width
。
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
export default {
name: "App",
data() {
return {
count: 0,
width: 0,
};
},
mounted() {
this.handleResize();
window.addEventListener("resize", this.handleResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleResize);
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
handleResize() {
this.width = window.innerWidth;
},
},
components: {
Count,
Width,
},
};
</script>
上面的代码片段表示一个名为 App
的 Vue 单文件组件 (SFC)。
<template>
部分定义了组件的标记。在本例中,它包含一个带有类“App”的 <div>
元素,它包装了两个子组件:<Count>
和 <Width>
。这些子组件使用 Vue 的属性绑定语法 (:count
、:increment
、:decrement
和 :width
) 传递了某些属性。
<script>
部分包含组件的 JavaScript 代码。它首先从各自的文件导入 Count
和 Width
组件。export default
语句用于导出组件定义。在组件定义中,我们有
data
方法,它返回一个对象,其中包含组件的初始数据属性,即初始化为 0 的count
和width
。mounted()
生命周期钩子用于在组件已挂载到 DOM 中后执行代码。在本例中,它调用handleResize()
方法并为 resize 事件添加事件监听器。beforeUnmount()
生命周期钩子用于在组件卸载并销毁之前执行代码。在这里,它删除了 resize 事件的事件监听器。methods
对象包含组件的方法。它定义了increment()
、decrement()
和handleResize()
方法,这些方法基于某些事件或操作来操作 count 和 width 数据属性。
1<template>2 <div class="App">3 <Count :count="count" :increment="increment" :decrement="decrement" />4 <div id="divider" />5 <Width :width="width" />6 </div>7 </template>89 <script>10 import Count from "./components/Count.vue";11 import Width from "./components/Width.vue";1213 export default {14 name: "App",15 data() {16 return {17 count: 0,18 width: 0,19 };20 },21 mounted() {22 this.handleResize();23 window.addEventListener("resize", this.handleResize);24 },25 beforeUnmount() {26 window.removeEventListener("resize", this.handleResize);27 },28 methods: {29 increment() {30 this.count++;31 },32 decrement() {33 this.count--;34 },35 handleResize() {36 this.width = window.innerWidth;37 },38 },39 components: {40 Count,41 Width,42 },43 };44 </script>
当应用程序运行时,当前计数和窗口的内部宽度会实时显示。用户可以通过使用 <Count>
组件中的按钮来增加和减少计数来与组件交互。
同样,每当窗口大小调整时,宽度会自动更新。
App.vue
单文件组件的结构可以可视化为以下内容
即使这个组件的大小很小,它内部的逻辑已经交织在一起。有些部分专门用于计数器的功能,而另一些部分则与宽度逻辑有关。随着组件的增长,组织和定位组件内的相关逻辑将变得更加困难。
为了解决这些挑战,Vue 团队在 Vue v3 中引入了 Composition API。
Composition API
Composition API 可以看作是 **提供代表 Vue 核心功能的独立函数的 API**。这些函数主要用在单个 setup()
选项中,该选项充当使用 Composition API 的入口点。
<!-- Template -->
<script>
export default {
name: "MyComponent",
setup() {
// the setup function
},
};
</script>
<!-- Styles -->
setup()
函数在组件创建之前以及组件的 props 可用时执行。
使用 Composition API,我们可以导入独立函数来帮助我们在组件中访问 Vue 的核心功能。让我们重写上面我们看到的计数器和宽度示例,同时依靠 Composition API 语法。
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from "vue";
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
export default {
name: "App",
setup() {
const count = ref(0);
const width = ref(0);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const handleResize = () => {
width.value = window.innerWidth;
};
onMounted(() => {
handleResize();
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
});
return {
count,
width,
increment,
decrement,
};
},
components: {
Count,
Width,
},
};
</script>
我们组件的 <template>
保持不变,但在组件的 <script>
部分,我们现在使用 setup()
函数使用 Composition API。
在 setup()
函数内部,我们
- 使用
ref()
函数定义count
和width
响应式变量——该函数接受单个基本值(例如字符串、数字等)并返回响应式/可变对象。 - 我们还定义了自定义函数
increment()
、decrement()
和handleResize()
。这些函数类似于我们在之前的选项 API 示例中定义的方法。 - 我们使用
onMounted()
生命周期函数来调用自定义handleResize()
函数,并在组件挂载时为 resize 事件添加事件监听器。同样,我们使用onBeforeUnmount()
生命周期函数在组件卸载之前删除 resize 事件的事件监听器。 - 然后返回在
setup()
函数中定义的响应式变量和函数,使它们可以在组件模板中访问。
1<template>2 <div class="App">3 <Count :count="count" :increment="increment" :decrement="decrement" />4 <div id="divider" />5 <Width :width="width" />6 </div>7</template>89<script>10import { ref, onMounted, onBeforeUnmount } from "vue";11import Count from "./components/Count.vue";12import Width from "./components/Width.vue";1314export default {15 name: "App",16 setup() {17 const count = ref(0);18 const width = ref(0);1920 const increment = () => {21 count.value++;22 };2324 const decrement = () => {25 count.value--;26 };2728 const handleResize = () => {29 width.value = window.innerWidth;30 };3132 onMounted(() => {33 handleResize();34 window.addEventListener("resize", handleResize);35 });3637 onBeforeUnmount(() => {38 window.removeEventListener("resize", handleResize);39 });4041 return {42 count,43 width,44 increment,45 decrement,46 };47 },48 components: {49 Count,50 Width,51 },52};53</script>
可组合组件
使用我们之前的代码示例,有人可能仍然想知道 setup()
函数如何为开发提供任何优势,因为它似乎只是要求我们将组件选项声明在一个函数中。
采用 Composition API 的一项重大优势是 **能够提取和重用组件之间共享的逻辑**。这是由以下事实驱动的:我们可以简单地声明自己的函数,这些函数使用 Vue 的全局可用组合函数,并使我们的函数 *能够轻松地在多个组件中使用以实现相同的结果*。
让我们进一步研究之前的计数器和宽度示例,创建一个可组合函数,它封装共享逻辑,可以在组件之间重用。
首先,让我们创建一个名为 useCounter
的可组合函数,这是一个封装计数器功能的可组合函数,并返回 count
的当前值、一个 increment()
方法和一个 decrement()
方法。
按照惯例,可组合函数名称以“use”关键字开头。
import { ref } from "vue";
export function useCounter(initialCount = 0) {
const count = ref(initialCount);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return {
count,
increment,
decrement,
};
}
同样,我们可以创建一个名为 useWidth()
的可组合函数,它封装了应用程序的宽度功能。
import { ref, onMounted, onBeforeUnmount } from "vue";
export function useWidth() {
const width = ref(0);
function handleResize() {
width.value = window.innerWidth;
}
onMounted(() => {
handleResize();
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
});
return {
width,
};
}
在我们的 App
组件中,我们现在可以使用可组合函数来实现相同的结果
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
import { useCounter } from "./composables/useCounter";
import { useWidth } from "./composables/useWidth";
export default {
name: "App",
components: {
Count,
Width,
},
setup() {
const { count, increment, decrement } = useCounter(0);
const { width } = useWidth();
return {
count,
increment,
decrement,
width,
};
},
};
</script>
通过这些更改,我们的应用程序将像以前一样运行,但在更可组合的设置中运行。
1<template>2 <div class="App">3 <Count :count="count" :increment="increment" :decrement="decrement" />4 <div id="divider" />5 <Width :width="width" />6 </div>7</template>89<script>10import Count from "./components/Count.vue";11import Width from "./components/Width.vue";12import { useCounter } from "./composables/useCounter";13import { useWidth } from "./composables/useWidth";1415export default {16 name: "App",17 components: {18 Count,19 Width,20 },21 setup() {22 const { count, increment, decrement } = useCounter(0);23 const { width } = useWidth();2425 return {26 count,27 increment,28 decrement,29 width,30 };31 },32};33</script>
通过在 Composition API 设置中使用可组合函数,我们能够将应用程序的上下文分解成更小的、可重用的部分,从而分离了逻辑。
让我们将我们刚刚进行的更改与最初的选项 API 示例组件进行比较,以可视化这些更改。
在 Vue 中使用可组合函数使我们能够将组件的逻辑分成几个更小的部分。重用相同的 stateful 逻辑现在变得容易,因为我们不再局限于在选项 API 中的特定选项中组织代码。
使用可组合函数,我们可以灵活地跨组件提取和重用共享逻辑。这种关注点分离使我们能够专注于每个可组合函数中的特定功能,从而使我们的代码 **更模块化、更易于维护**。
通过将逻辑分解成更小、可重用的部分,我们可以使用这些可组合函数来组合组件,将必要的功能整合在一起,而无需重复代码。这种方法促进了 **代码可重用性**,并降低了代码重复和不一致的风险。
此外,使用 Composition API 提高了组件逻辑的 **可读性** 和 **可理解性**。每个可组合函数都封装了组件行为的特定方面,使推理和测试变得更容易。它还允许团队成员之间更容易协作,因为代码变得更有条理和组织性。
最后,使用 Composition API 构建 Vue 应用程序可以实现 **更好的类型推断**。由于 Composition API 帮助我们使用变量和标准 JavaScript 函数来处理组件逻辑,因此使用 TypeScript 等静态类型系统来构建大型 Vue 应用程序变得容易得多!