渲染模式
无渲染组件
无渲染组件是 Vue 中的一种模式,它 **将组件的逻辑与呈现分离**。这种模式提供了一种封装功能的方法,而 *不规定组件的视觉表现形式*。换句话说,无渲染组件只关注逻辑和行为,而将渲染留给父组件。
当我们需要创建可重用逻辑,该逻辑可以应用于不同的 UI 实现时,无渲染组件特别有用。通过将逻辑抽象成无渲染组件,我们可以轻松地将其重用在各种上下文中,而无需重复代码。如果你现在仍然感到困惑,别担心!让我们用一个例子深入了解这个概念。
切换、切换、切换
想象一下,您有一个切换 UI 元素需要在应用程序的不同部分使用,但每个实例可能具有不同的视觉表现形式。一些切换可能以按钮的形式显示,而另一些可能以复选框或开关的形式显示。
我们可以为上面的示例创建三个不同的切换组件,但是,我们可以观察到每个切换元素具有相同的逻辑和行为。每个切换都有一个非活动状态和活动状态,这些状态通过一个组件数据属性(例如 checked
)进行跟踪。当点击一个切换时,它的组件状态从非活动切换到活动,反之亦然(即 checked = !checked
)。
以下是一张视觉图,显示了每个组件的 <template>
和 <script>
部分是如何构建的
我们可以立即看到,我们可以通过提取公共逻辑和行为来创建一个更可重用的模式,这样我们就不必在每个单独的切换组件中重复定义状态和切换方法。这是一个使用 可组合函数 的绝佳案例,因为可组合函数将允许我们在不同的切换组件中封装和共享公共状态逻辑。
useCheckboxToggle:
import { ref } from "vue";
export function useCheckboxToggle() {
const checkbox = ref(false);
const toggleCheckbox = () => {
checkbox.value = !checkbox.value;
};
return {
checkbox,
toggleCheckbox,
};
}
一个切换组件:
<template>
<div class="comp">
<label class="switch">
<input type="checkbox" :value="checkbox" @click="toggleCheckbox" />
<div class="slider rounded" :class="checkbox ? 'active' : ''"></div>
</label>
</div>
</template>
<script setup>
import { useCheckboxToggle } from "./composables/useCheckboxToggle";
const { checkbox, toggleCheckbox } = useCheckboxToggle();
</script>
尽管上述方法非常适合我们的用例,但 Vue 为我们提供了另一种模式,关于如何在保持状态逻辑与渲染分离的同时重用它。
无渲染组件
无渲染组件背后的主要思想是创建一个组件,该组件本身不渲染任何 HTML 或 UI 元素,但将内部状态和方法暴露给其父组件。然后,父组件负责根据无渲染组件暴露的数据和行为渲染适当的 UI。
父组件能够决定应该渲染什么,这是通过称为 **插槽** 的概念实现的。
插槽允许父组件将模板内容注入到子组件中,可以认为它们类似于道具,但不是传递 JavaScript 值,而是允许将模板片段传递给子组件。
让我们开始创建我们的无渲染切换组件。在组件的 <script>
部分,我们将拥有负责切换 checkbox
状态值的 stateful 逻辑。
<script setup>
import { ref } from "vue";
const checkbox = ref(false);
const toggleCheckbox = () => {
checkbox.value = !checkbox.value;
};
</script>
在组件的 <template>
部分,我们将使用特殊的 <slot>
元素来指示父级提供的模板内容将位于此处。
<template>
<slot></slot>
</template>
<script setup>
import { ref } from "vue";
const checkbox = ref(false);
const toggleCheckbox = () => {
checkbox.value = !checkbox.value;
};
</script>
当我们声明要在子组件中渲染的模板时,我们将需要 checkbox
和 toggleCheckbox()
属性在父组件中可用。为此,我们可以将这些属性传递给 <slot></slot>
出口,就像我们将道具传递给组件一样。
<template>
<slot :checkbox="checkbox" :toggleCheckbox="toggleCheckbox"></slot>
</template>
<script setup>
import { ref } from "vue";
const checkbox = ref(false);
const toggleCheckbox = () => {
checkbox.value = !checkbox.value;
};
</script>
在父组件中,我们现在将能够引用 checkbox
和 toggleCheckbox()
属性,因为我们决定了我们想要如何渲染子组件。
注意我们创建的组件没有自己的模板?这就是使其成为 **无渲染组件** 的原因,即一个只关注逻辑和行为,而将渲染留给其父组件的组件。
在父组件中,我们现在将尝试渲染三个不同的切换元素,每个元素都有自己独特的用户体验。我们首先导入上面创建的无渲染 ToggleComponent
组件。
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
现在,我们可以尝试渲染 <ToggleComponent>
,我们将放在组件子级中的任何内容都将是渲染的模板插槽内容。
<template>
<ToggleComponent>
<!-- slot content -->
<!-- (i.e. what gets rendered as the ToggleComponent template) -->
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
当我们渲染组件插槽内容时,我们将需要访问子组件范围内的属性(checkbox
和 toggleCheckbox()
)。由于我们之前将这些属性传递给了插槽出口(<slot :checkbox="checkbox" :toggleCheckbox="toggleCheckbox"></slot>
),我们可以使用 v-slot
指令来接收这些插槽道具。
<template>
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<!-- slot content -->
<!-- (i.e. what gets rendered as the ToggleComponent template) -->
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
有了相关的插槽道具,我们现在可以渲染第一个切换元素。此切换元素将是一个开关切换,它根据 checkbox
属性的值在非活动状态和活动状态之间切换。
<template>
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<label class="switch">
<input type="checkbox" :value="checkbox" @click="toggleCheckbox" />
<div class="slider rounded" :class="checkbox ? 'active' : ''"></div>
</label>
</div>
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
保存更改后,我们将看到应用程序中的开关切换。
我们可以继续以非常类似的方式创建另外两个切换元素。第二个切换元素将是一个按钮,当点击时,它在 Toggle | Yes 😀
和 Toggle | No 😔
的文本之间切换。
<template>
<!-- Toggle element 1 -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<label class="switch">
<input type="checkbox" :value="checkbox" @click="toggleCheckbox" />
<div class="slider rounded" :class="checkbox ? 'active' : ''"></div>
</label>
</div>
</ToggleComponent>
<!-- Toggle element 2 -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<button class="toggle-button" @click="toggleCheckbox">
Toggle | <span>{{ checkbox ? "Yes 😀" : "No 😔" }}</span>
</button>
</div>
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
最后,我们的第三个切换元素将是两个选项卡式按钮,当点击其中任何一个按钮时,都会切换两个按钮的活动状态。
<template>
<!-- Toggle element 1 -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<label class="switch">
<input type="checkbox" :value="checkbox" @click="toggleCheckbox" />
<div class="slider rounded" :class="checkbox ? 'active' : ''"></div>
</label>
</div>
</ToggleComponent>
<!-- Toggle element 2 -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<button class="toggle-button" @click="toggleCheckbox">
Toggle | <span>{{ checkbox ? "Yes 😀" : "No 😔" }}</span>
</button>
</div>
</ToggleComponent>
<!-- Toggle element 3 -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<button
:class="['tab-button', { active: checkbox }]"
@click="toggleCheckbox"
>
On
</button>
<button
:class="['tab-button', { active: !checkbox }]"
@click="toggleCheckbox"
>
Off
</button>
</div>
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
保存这些更改后,我们将看到三个不同的切换元素,它们的外观不同,但具有相同的底层逻辑。
1<template>2 <slot :checkbox="checkbox" :toggleCheckbox="toggleCheckbox"></slot>3</template>45<script setup>6import { ref } from "vue";78const checkbox = ref(false);910/* eslint-disable-next-line no-unused-vars */11const toggleCheckbox = () => {12 checkbox.value = !checkbox.value;13};14</script>
可组合函数与无渲染组件
可组合函数和无渲染组件是 Vue 中的两种模式,它们提供了不同的方法来封装和重用逻辑。
我们在 之前的文章 中看到,可组合函数通常由返回响应式数据和方法的函数组成,这些函数可以导入并在不同的组件中使用。另一方面,无渲染组件专注于通过让父组件负责根据无渲染组件暴露的数据和行为渲染适当的 UI 来分离组件的逻辑与呈现。
Vue 文档 建议尽可能使用可组合函数,因为无渲染组件模式有时会由于创建的额外组件实例数量而导致性能开销。但是,无渲染组件有时在我们需要对渲染进行细粒度控制或需要重用逻辑和视觉布局的情况下可能会有益。