在 无渲染组件(待更新) 这篇文章中,我们了解了无渲染组件如何帮助将组件的逻辑与其表示分离。当我们需要创建可应用于不同 UI 实现的可重用逻辑时,这会变得很有用。
无渲染组件还允许我们利用另一种有用的模式,称为数据提供者模式。
数据提供者模式
数据提供者模式是一种设计模式,它是 Vue 中的无渲染组件模式的一种补充,它专注于为组件提供数据和状态管理功能,而不关心数据如何渲染或展示。
在数据提供者模式中,数据提供者组件封装了用于获取、管理数据并将数据公开给其子组件的逻辑。然后,子组件可以使用这些数据并在自己的渲染或行为中使用它。
这种模式促进了关注点分离,因为数据提供者组件负责与数据相关的任务,而子组件可以专注于展示和交互。
让我们用一个例子来说明数据提供者模式。考虑一个简单的应用程序,它显示一个有趣的笑话的设置及其妙语。为了帮助我们随机显示不同的笑话,我们将使用免费的公共 API official-joke-api.appspot.com/random_joke ,它会返回 JSON 格式的随机笑话。
# https://official-joke-api.appspot.com/random_joke
{
"type": "general",
"setup": "How good are you at Power Point?",
"punchline": "I Excel at it.",
"id": 129
}
我们首先创建一个名为 DataProvider 的数据提供程序组件,它将负责从 API 中获取笑话。在组件的 <script> 部分中,我们将从 Vue 库导入 ref() 和 reactive() 函数,将接口 URL 赋值给常量,然后设置 data 和 loading 响应式属性来捕获 API 请求的数据和加载状态。
<script setup>
import { ref, reactive } from "vue";
const API_ENDPOINT_URL = "https://official-joke-api.appspot.com/random_joke";
const data = reactive({
setup: null,
punchline: null,
});
const loading = ref(false);
</script>
然后,我们将创建一个名为 fetchJoke() 的异步函数,负责从指定的 API 接口获取笑话。该函数将:
- 首先将
loading响应值设置为true,表示正在获取笑话。 - 使用
response.json()方法将 API 的返回转换为 JSON 格式。 - 从获取的请求数据中提取
setup和punchline值,并将它们分配给data对象中的相应属性。 - 最后,将
loading值设置回false,表示笑话已获取完毕。
经过这些更改,我们的 fetchJoke() 函数将如下所示:
<script setup>
import { ref, reactive } from "vue";
const API_ENDPOINT_URL = "https://official-joke-api.appspot.com/random_joke";
const data = reactive({
setup: null,
punchline: null,
});
const loading = ref(false);
const fetchJoke = async () => {
loading.value = true;
const response = await fetch(API_ENDPOINT_URL);
const responseData = await response.json();
data.setup = responseData.setup;
data.punchline = responseData.punchline;
loading.value = false;
};
fetchJoke();
</script>
注意到我们在 <script> 部分末尾调用了 fetchJoke() 函数吗?这可确保在渲染 DataProvider 组件时能立即获取笑话。
我们要做的最后一件事是使 data 和 loading 属性在 DataProvider 组件的使用者中可用。为此,我们可以将这些属性传递给将放置在 <template> 部分中的 <slot> 元素。
<template>
<slot :checkbox="checkbox" :toggleCheckbox="toggleCheckbox"></slot>
</template>
<script setup>
import { ref, reactive } from "vue";
const API_ENDPOINT_URL = "https://official-joke-api.appspot.com/random_joke";
const data = reactive({
setup: null,
punchline: null,
});
const loading = ref(false);
const fetchJoke = async () => {
loading.value = true;
const response = await fetch(API_ENDPOINT_URL);
const responseData = await response.json();
data.setup = responseData.setup;
data.punchline = responseData.punchline;
loading.value = false;
};
fetchJoke();
</script>
随着我们的无渲染数据提供程序组件的完成,我们现在可以在我们的应用程序中使用它。在父应用程序组件中,我们将导入 DataProvider 组件并将其放置在模板中。
<template>
<DataProvider v-slot="{ data, loading }">
<!-- ... -->
</DataProvider>
</template>
<script setup>
import DataProvider from "./components/DataProvider.vue";
</script>
通过简单地渲染 <DataProvider> 组件,我们向接口发出请求以获取笑话,并可以在帮助下访问请求的 data 和 loading 值 v-slot 指令的。
在 <DataProvider> 组件声明中,我们可以创建 UI,如果请求处于加载状态,该 UI 将显示加载消息,或者在数据可用时显示笑话设置和笑话。
<template>
<DataProvider v-slot="{ data, loading }">
<div class="joke-section">
<p v-if="loading">Joke is loading...</p>
<p v-if="!loading">{{ data.setup }}</p>
<p v-if="!loading">{{ data.punchline }}</p>
</div>
</DataProvider>
</template>
<script setup>
import DataProvider from "./components/DataProvider.vue";
</script>
保存更改时,我们将看到一条简短的加载消息,然后是一个随机笑话。
如果我们需要渲染笑话的另一个实例,甚至可能使用不同的模板,我们可以简单地重用 <DataProvider> 组件并创建我们想要显示的新的子元素。
<template>
<DataProvider v-slot="{ data, loading }">
<div class="joke-section">
<p v-if="loading">Joke is loading...</p>
<p v-if="!loading">{{ data.setup }}</p>
<p v-if="!loading">{{ data.punchline }}</p>
</div>
</DataProvider>
<DataProvider v-slot="{ data, loading }">
<p v-if="loading">Hold on one sec...</p>
<div v-else class="joke-section">
<details>
<summary>{{ data.setup }}</summary>
<p>{{ data.punchline }}</p>
</details>
</div>
</DataProvider>
</template>
<script setup>
import DataProvider from "./components/DataProvider.vue";
</script>
在我们新渲染的 UI 中,我们现在借助 HTML <details> 和 <summary> 元素将笑话放在详细信息元素中。
通过数据提供者模式,我们能够以解耦和可重用的方式管理数据并将其提供给不同的元素/组件。通过将 API 获取逻辑抽象为无渲染组件,我们可以在各种上下文中重用 API 数据的请求,而无需重复代码。
可以使用组合式函数吗?
是的!我们可以不使用数据提供者模式,而是利用组合式函数将获取逻辑提取到可重用函数中。
import { ref, reactive } from "vue";
const API_ENDPOINT_URL = "https://official-joke-api.appspot.com/random_joke";
export function useGetJoke() {
const data = reactive({
setup: null,
punchline: null,
});
const loading = ref(false);
const fetchJoke = async () => {
loading.value = true;
const response = await fetch(API_ENDPOINT_URL);
const responseData = await response.json();
data.setup = responseData.setup;
data.punchline = responseData.punchline;
loading.value = false;
};
fetchJoke();
return { data, loading };
}
在我们的组件实例中,我们可以导入并使用可组合函数来获取特定请求的 data 和 loading 状态。
<template>
<div class="joke-section">
<p v-if="loading">Joke is loading...</p>
<p v-if="!loading">{{ data.setup }}</p>
<p v-if="!loading">{{ data.punchline }}</p>
</div>
</template>
<script setup>
import { useGetJoke } from "./composables/useGetJoke";
const { data, loading } = useGetJoke();
</script>
我们的应用程序现在的行为就像之前的数据提供程序示例一样。
数据提供者模式通过让父组件根据公开的数据和无渲染组件的行为来渲染适当的 UI,从而帮助将组件的逻辑与其展示分离。然而,由于能够在 Vue 3 中创建可重用的可组合函数,因此组合式函数也可以用于大多数可以使用数据提供者模式的情况。
在考虑使用数据提供者模式还是使用可组合函数时,我们建议尽可能使用可组合函数,因为它避免了每次需要完成数据获取时都需要渲染组件实例(这可能会导致性能开销)。
此外,如果您使用 Pinia 等状态管理工具来管理向组件提供数据的方式,那么您很可能会在 store 的 actions() 中发出 API 请求。有了这种状态管理模式,使用数据提供者组件模式的需求就变得不那么重要了。