- Published on
Vue - 无渲染组件
- Authors
- Name
- Deng Hua
文章翻译至: Renderless components
目录
什么是无渲染组件(renderless components)
无渲染组件是 Vue 中的一种模式,它将组件的逻辑与表现分离。该模式提供了一种封装功能的方法,而无需指定组件的视觉表示。
换句话说,无渲染组件仅关注逻辑和行为,而将UI留给父组件。
当我们需要创建可应用于不同 UI 的可重用逻辑时,无渲染组件非常实用。通过将逻辑抽象为无渲染组件,我们可以轻松地在各种上下文中复用它,而无需重复写代码。
如果此时您仍然感到困惑,别担心!让我们通过一个例子更深入地探讨这个概念。
重复的逻辑
想象一下,您有一个toggle UI元素,需要在应用中的不同位置使用,但每个实例可能有不同的视觉表现。一些toggle可能显示为button,而另一些可能显示为checkbox或switch。
我们当然可以为上面的示例创建三个不同的toggle组件,但是,我们也可以观察到每个toggle元素具有相同的逻辑和行为。
每个toggle都有一个非活动和活动状态,可以通过组件内的状态变量(例如 checked
)进行跟踪。单击toggle按钮时,其组件状态将从非活动状态变为活动状态,反之亦然(即 checked = !checked
)。
下面的视觉效果显示了每个组件的 <template>
和 <script>
块的构造方式:
看到这,我们立即可以想到的是,可以通过提取通用逻辑和行为来创建更通用的组件,这样我们就不必在每个单独的切换组件中重复定义状态和切换方法。这是使用可组合项的一个很好的例子,因为可组合项将允许我们在不同的toggle组件之间封装和共享通用的状态逻辑。
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。
由父组件来决定渲染内容是什么,可以通过插槽来实现。
插槽允许父组件将模板内容注入到子组件中,类似于 props,不过它们并不传递 JavaScript 值,而是将模板片段传递给子组件。
让我们开始创建我们的toggle无渲染组件。在组件的 <script>
部分,我们首先创建toggle复选框的状态和状态切换的逻辑。
<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()
属性。
<!-- ToggleComponent.vue -->
<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()
,就像我们决定如何呈现子组件一样。
可以看到,我们创建的组件并没有自己的模板内容,这就是它称为无渲染组件的原因,该组件仅专注于逻辑和行为,将UI表现留给其父组件。
在父组件中,我们现在将尝试渲染三个不同的toggle元素,每个元素都有自己独特的用户体验。我们首先导入上面创建的无渲染 ToggleComponent
组件。
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
现在,我们可以尝试渲染 <ToggleComponent>
,传入你需要显示的模板内容。
<template>
<ToggleComponent>
<!-- 插槽内容 -->
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
当我们渲染slot内容时,我们需要访问子组件上下文的属性( checkbox
和 toggleCheckbox()
)。
由于我们之前已将这些属性传递给插槽 ,因此我们可以使用 v-slot
指令来接收这些插槽属性。
<template>
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<!-- 插槽内容 -->
</ToggleComponent>
</template>
<script setup>
import ToggleComponent from "./components/ToggleComponent";
</script>
注意: 这里是将定义<slot>
所在组件的作用域内的变量和状态,传递给使用该插槽的组件所处的作用域。
如果看不懂的话,借用官方文档的代码和图来说明:
<!-- 定义<MyComponent>组件,内含一个插槽 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<!-- 使用<MyComponent>组件,这里接受插槽传递出来的数据 -->
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
接着之前的示例代码。
有了可用的相关插槽和props,我们现在可以渲染第一个toggle元素。此toggle元素将是一个switch开关,根据 checkbox
属性的值从非活动状态切换到活动状态。
<template>
<!-- 这里使用对象解构,等价 v-slot="slotProps" ..... slotProps.checkbox -->
<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 lang="ts">
import ToggleComponent from './ToggleComponent.vue';
</script>
保存更改时,我们将在应用程序中看到开关切换。
我们可以继续以相似的方式创建其他两个toggle元素。
第二个切换元素将是一个button,单击该按钮时,会在 Toggle | Yes 😀 和 Toggle | No 😔 文本之间切换。
第三个Toggle元素将是两个选项卡式按钮,单击其中一个按钮即可切换两个按钮的活动状态。
最终三个Toggle元素的代码:
<template>
<!-- Toggle Checkbox -->
<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 Button -->
<ToggleComponent v-slot="{ checkbox, toggleCheckbox }">
<div class="comp">
<button class="toggle-button" @click="toggleCheckbox">
Toggle | <span>{{ checkbox ? "Yes 😀" : "No 😔" }}</span>
</button>
</div>
</ToggleComponent>
<!-- Toggle Tab -->
<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 lang="ts">
import ToggleComponent from './ToggleComponent.vue';
</script>
End.