无渲染组件
无渲染组件是 Vue 的一种模式,它将组件的逻辑和表现分离。该模式提供了一种封装组件功能的方法,而无需关心组件的视觉表现。换言之,无渲染组件只关注逻辑和行为,而将渲染的工作交给它的父组件。
在我们创建在多个视图中可复用的逻辑时,无渲染组件在十分有用。通过将逻辑抽象为无渲染组件,我们可以轻松地在各种场景下复用,从而避免了重复的代码。如果你仍然很困惑,别着急,我们通过一个例子来深入了解。
三个切换按钮
想想你有一个表示切换的 UI 组件,需要在你应用程序的不同位置使用它,但是每个实例可能需要有不同的视觉效果。有的可能长得像一个按钮,有的像一个复选框,有的像开关。
我们可以直接创建三个不同的切换组件,但是我们可以观察到,每个切换的元素都具有相同的逻辑和行为。每个切换元素都有一个激活和未激活状态(例如 checked
)。当点击切换元素,它的激活状态就会切换到另一边(即 checked = !checked
)。
下图中我们可以看到,每一种切换组件的 <template>
和 <script>
都是如何编写的:
我们可以通过提取公共的逻辑和行为来创建一个具有可复用性的模式,这样我们就不需要在每个单独的组件中都重复定义相同的状态和切换方法了。这是一个极好的使用 组合式 的案例,因为我们可以使用组合式来封装每个切换组件中相同的状态和逻辑。
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 元素,但是将其内部的状态和方法暴露给它的父组件。父组件基于无渲染组件提供的数据和行为进行渲染。
可以让子组件把渲染什么的决定权交给父组件,这个能力来自于 插槽
。
插槽允许父组件向子组件传递模板内容,这种行为可以看作是类似 props,但实际上不是传递了 JavaScript 的值,而是传递了模板。
接下来开始创建我们的无渲染切换组件。在组件的 <script>
部分,我们定义一个负责记录切换状态的值 checkbox
。
<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>
,就像我们将 props 传递给组件一样。
<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>
<!-- 插槽内容 -->
<!-- (即 作为 ToggleComponent 的模板渲染的内容) -->
</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 }">
<!-- 插槽内容 -->
<!-- (即 作为 ToggleComponent 的模板渲染的内容) -->
</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>
<!-- 切换元素 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>
<!-- 切换元素 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>
<!-- 切换元素 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>
<!-- 切换元素 2 -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<button class="toggle-button" @click="toggleCheckbox">
Toggle | <span>{{ checkbox ? "Yes 😀" : "No 😔" }}</span>
</button>
</div>
</ToggleComponent>
<!-- 切换元素 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>
保存后,我们可以看到三个不同表现形式的切换组件,但它们均基于同一套底层逻辑。
在演练场中查看代码组合式 和 无渲染组件
组合式和无渲染组件是 Vue 中的两种模式,它们提供了不同的方法来封装和复用逻辑。
我们已经在之前的文章 中看到了,组合式通常由响应式状态和方法组成,这些方法可以在不同的组件中导入并使用。另一方面,无渲染组件更关心将组件的逻辑和表现形式分离,让父组件使用无渲染组件暴露的数据和行为来渲染 UI。
Vue 的官方文档 建议尽可能使用组合式,因为无渲染组件模式有时会由于创建额外的组件实例导致额外的性能开销。然而,有时候我们需要对渲染进行更精细的控制,或是需要复用逻辑和布局的情况下,无渲染组件会很有用。