vue3 插槽一览和封装新闻卡片

在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应用。

综合示例

让我们通过引入插槽的概念来改造这个新闻/文章卡片组件,使其更加灵活和可复用。我们将添加以下功能:

  1. 动态标签插槽:允许父组件自定义如何显示标签。
  2. 自定义内容插槽:为新闻/文章卡片底部提供一个插槽,使得父组件可以添加自定义内容(例如,更多的按钮或其他信息)。

改造组件模板

首先,我们在组件的模板中添加两个插槽:tagscustom-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就忽略吧

相关推荐
小白CAD1 小时前
前端vue打印后端对象为[object,object]
前端·javascript·vue.js
续亮~4 小时前
6、Redis系统-数据结构-05-整数
java·前端·数据结构·redis·算法
顶顶年华正版软件官方5 小时前
剪辑抽帧技巧有哪些 剪辑抽帧怎么做视频 剪辑抽帧补帧怎么操作 剪辑抽帧有什么用 视频剪辑哪个软件好用在哪里学
前端·音视频·视频·会声会影·视频剪辑软件·视频剪辑教程·剪辑抽帧技巧
程序员云翼6 小时前
7-理财平台
java·vue.js·spring boot·后端·毕设
托尼沙滩裤6 小时前
【js面试题】js的数据结构
前端·javascript·数据结构
不熬夜的臭宝7 小时前
每天10个vue面试题(一)
前端·vue.js·面试
朝阳397 小时前
vue3【实战】来回拖拽放置图片
javascript·vue.js
不如喫茶去7 小时前
VUE自定义新增、复制、删除dom元素
前端·javascript·vue.js
长而不宰7 小时前
vue3+electron项目搭建,遇到的坑
前端·vue.js·electron
阿垚啊7 小时前
vue事件参数
前端·javascript·vue.js