在Vue 3中,插槽(Slots)是一种强大的模式,用于从父组件向子组件传递内容。它们允许你将组件的一部分内容定义在父组件中,而这部分内容将在子组件的指定位置呈现。与Vue 2相比,Vue 3在插槽的使用和语法上做了一些改进,尤其是在组合式 API 的使用上。
基础知识
基本插槽
基本插槽的使用非常简单。你只需要在子组件的模板中使用<slot></slot>
标签定义一个插槽,然后在父组件中,将你希望放入插槽的内容放在子组件标签的内部。
子组件 (ChildComponent.vue
):
vue
<template>
<div>
<!-- 定义一个插槽 -->
<slot></slot>
</div>
</template>
父组件:
vue
<template>
<div>
<ChildComponent>
<!-- 这里的内容将被放入 ChildComponent 的插槽中 -->
<p>这是通过插槽传递的内容。</p>
</ChildComponent>
</div>
</template>
具名插槽
当你需要在一个子组件中定义多个插槽时,具名插槽就非常有用。你可以通过给<slot>
标签一个name
属性来命名它。
子组件:
vue
<template>
<div>
<slot name="header"></slot>
<slot name="footer"></slot>
</div>
</template>
父组件:
vue
<template>
<ChildComponent>
<template v-slot:header>
<h1>这是头部内容</h1>
</template>
<template v-slot:footer>
<p>这是底部内容</p>
</template>
</ChildComponent>
</template>
作用域插槽
作用域插槽允许你将数据从子组件传递到插槽内容中。这对于创建高度可定制的组件非常有用。
子组件:
vue
<template>
<ul>
<li v-for="item in items" :key="item.id">
<!-- 使用作用域插槽传递 item 数据 -->
<slot name="item" :item="item">{{ item.text }}</slot>
</li>
</ul>
</template>
<script>
export default {
data() {
return {
items: [{ id: 1, text: 'Item 1' }, { id: 2, text: 'Item 2' }]
};
}
};
</script>
父组件:
vue
<template>
<ChildComponent>
<template v-slot:item="slotProps">
<p>{{ slotProps.item.text }}</p>
</template>
</ChildComponent>
</template>
在Vue 3中,v-slot
可以简写为#
,例如v-slot:header
可以写作#header
。
插槽是Vue组件化开发中的重要概念,它们让父子组件之间的内容分发更加灵活和强大。通过基本插槽、具名插槽和作用域插槽的使用,你可以构建高度可重用和维护的Vue应用。
动态插槽名
在使用具名插槽时,你可能有时候需要根据条件动态地选择插槽。Vue 3允许你通过绑定v-slot
指令的参数来实现这一点:
vue
<template>
<ChildComponent>
<template v-slot:[dynamicSlotName]>
<p>这是动态决定的插槽内容。</p>
</template>
</ChildComponent>
</template>
<script>
export default {
data() {
return {
dynamicSlotName: 'header' // 可以是 'header' 或 'footer',根据需要动态更改
};
}
};
</script>
插槽的默认内容
在定义插槽时,你可以为插槽提供默认内容。如果父组件没有提供相应的插槽内容,将显示默认内容。这在创建可复用组件时非常有用,因为它允许组件有一个合理的默认行为。
vue
<template>
<div>
<!-- 如果父组件没有提供内容,将显示此默认内容 -->
<slot>默认插槽内容</slot>
</div>
</template>
插槽的解构
在作用域插槽的使用中,有时候传递给插槽的数据结构可能相当复杂。Vue 3允许你在父组件中解构这些属性,使得模板代码更加清晰。
vue
<template>
<ChildComponent>
<template v-slot:item="{ item }">
<p>{{ item.text }}</p>
</template>
</ChildComponent>
</template>
插槽和组合式API
在Vue 3中,随着组合式API的引入,管理插槽变得更加灵活。你可以使用slots
属性在组合式API的setup
函数中访问插槽,这为程序化地工作与插槽提供了更多的可能性。
javascript
import { defineComponent, h } from 'vue';
export default defineComponent({
setup(props, { slots }) {
// 现在可以在setup函数中访问slots
return () => h('div', [slots.default ? slots.default() : '']);
}
});
通过这些高级技巧和Vue 3的强大功能,你可以创建出更加动态、可复用和高效的Vue应用。
综合示例
让我们通过引入插槽的概念来改造这个新闻/文章卡片组件,使其更加灵活和可复用。我们将添加以下功能:
- 动态标签插槽:允许父组件自定义如何显示标签。
- 自定义内容插槽:为新闻/文章卡片底部提供一个插槽,使得父组件可以添加自定义内容(例如,更多的按钮或其他信息)。
改造组件模板
首先,我们在组件的模板中添加两个插槽:tags
和custom-content
。
vue
<template>
<div class="base-new-card">
<div class="base-new-crad_l">
<div class="base-new-card_l-title">
<h3 class="one-line">{{ title }}</h3>
<div class="base-new-card_l-synopsis two-line">{{ synopsis }}</div>
</div>
<div class="base-new-card_l_b">
<div class="base-new-card_l_b_info text-grad">
<span class="cuIcon-attention margin-right-s"></span
>{{ count?.read || 0 }}
<span class="cuIcon-like margin-right-s margin-left-l"></span
>{{ count?.admire || 0 }}
</div>
<div class="base-new-card_l_b_tag">
<!-- 动态标签插槽 -->
<slot name="tags" :tags="tag">
<!-- 默认内容 -->
<div
v-for="(item, index) in tag.slice(0, 5)"
:key="index"
class="cu-tag light bg-grey margin-right-s"
>
{{ item.name }}
</div>
</slot>
</div>
</div>
</div>
<div class="base-new-card_r" :class="getImage('bg')">
<img
style="width: 140px; height: 100px; object-fit: scale-down"
:src="getImage()"
/>
</div>
</div>
<!-- 自定义内容插槽 -->
<div v-if="$slots['card-footer']">
<slot name="card-footer"></slot>
</div>
</template>
<script lang="ts" setup>
const coverDict = {
python: {
bg: "bg-grey",
url: "/python.png",
},
web: {
bg: "bg-js",
url: "/javascript.png",
},
golang: {
bg: "bg-cyan",
url: "/go.svg",
},
node: {
bg: "bg-green",
url: "/node.svg",
},
ai: {
bg: "bg-green",
url: "/python.png",
},
};
const defimage =
"https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg";
const getImage = computed(() => {
return (mode: string = "url") => {
switch (mode) {
case "url":
if (props.type && props.type in coverDict) {
return (coverDict as any)[props.type].url;
} else {
return defimage;
}
case "bg":
if (props.type && props.type in coverDict) {
return (coverDict as any)[props.type].bg;
} else {
return "bg-black";
}
}
};
});
const props = withDefaults(
defineProps<{
title: string;
synopsis?: string;
author?: string[] | string;
coverUrl?: string;
type?: string | "python" | "web" | "golang" | "node";
count?: {
read: number | string;
admire: number | string;
};
tag?: {
id: number;
name: string;
}[];
}>(),
{
synopsis: "无",
tag: function () {
return [];
},
}
);
</script>
<style scoped>
.bg-js {
background: #f0dc4e;
color: #fff;
}
.base-new-card {
display: flex;
justify-content: space-between;
padding: 15px;
border-bottom: #6666662c solid 1px;
}
.base-new-crad_l {
display: flex;
justify-content: space-between;
flex-direction: column;
width: 100%;
max-width: 420px;
margin-right: 20px;
}
.base-new-card_l-title {
color: #000000d8;
font-size: 14px;
font-weight: 600;
}
.base-new-card_l-synopsis {
font-size: 12px;
font-weight: 400;
color: #666;
}
.base-new-card_l_b {
display: flex;
justify-content: space-between;
}
.base-new-card_l_b_tag .cu-tag {
padding: 3px 5px 1px 5px;
border-radius: 2px;
font-size: 12px;
}
</style>
使用改造后的组件
父组件可以这样使用改造后的新闻/文章卡片组件,通过插槽来自定义标签显示和添加自定义内容。
vue
<template>
<div>
<BaseNewCard
v-for="(item, index) in newsList.data"
:key="index"
:title="item.title"
:author="item.author"
:synopsis="item.summary"
:count="item.count"
:type="item.articles_category.name"
:tag="item.articles_article_tags"
>
<template v-slot:tags="{ tags }">
<div v-for="(tag, index) in tags" :key="index" class="custom-tag-style">
{{ tag.name }}
</div>
</template>
<!-- 使用自定义内容插槽 -->
<template v-slot:card-footer>
<UButton>更多</UButton>
</template>
</BaseNewCard>
</div>
</template>
<script lang="ts" setup>
const newsList = reactive({
data: [] as any,
hasNextPage: true,
nextCursor: 0,
});
const getArticleList = () => {
$fetch("/api/article").then((res) => {
newsList.data = (res as any)?.data ?? [];
newsList.nextCursor = res?.nextCursor ?? 0;
newsList.hasNextPage = res?.hasNextPage ?? false;
});
};
getArticleList();
</script>
这样,我们就通过Vue 3的插槽功能,使得新闻/文章卡片组件更加灵活和可复用。父组件可以根据需要自定义标签的显示方式和添加额外的内容,而无需修改新闻/文章卡片组件本身。
改造前
改造后
总结
我的意思是,不如不改[手动狗头] 今天的主要内容是了解和加深vue3的插槽,这丑陋的CSS就忽略吧