【Vue】【内置元素】component && slot && template

155 阅读7分钟

前言

今天,我们就来唠唠Vue的这几个元素<component><slot> 和 <template>

你们看官网里也说了这三不是组件!不是组件!!不是组件!!!(重要的事说三遍)

image.png

一、<component>

<component>: 用于渲染动态组件或元素 的元组件

  • 作用:配合 is 动态动态渲染组件。通常在tab页切换,多操作页面中使用
  • is
    • is是字符串时,它既可以是 HTML 标签名,也可以是组件的注册名
    • is 也可以直接绑定到组件的定义

在Vue中,component可以作为一个占位符使用,就像template一样,不同的是component是组件的占位符,主要用于展示不同的组件,即component就是一个动态组件

<component :is="currentRole" ref="componentscjk" @refreshListTree="refreshListTree" />

如果对两个组件分别添加 beforeDestroy 生命周期函数,当相互切换时,其中的一个组件被销毁。所以,当两个组件进行状态切换时,组件的状态是:不断的创建与销毁的过程。

如果要把组件缓存下来,可以在动态组件上使用vue的另一个内置组件 KeepAlive

<keep-alive :max="10">
    <component :is="currentRole" ref="componentscjk" @refreshListTree="refreshListTree" />
</keep-alive>

二、<slot>

2.1 <slot>介绍

<slot>:表示模板的插槽内容出口

<slot> 元素可以使用 name attribute 来指定插槽名。当没有指定 name 时,将会渲染默认插槽。传递给插槽元素的附加 attributes 将作为插槽 props,传递给父级中定义的作用域插槽。

元素本身将被其所匹配的插槽内容替换。

Vue 模板里的 <slot> 元素会被编译到 JavaScript,因此不要与原生 <slot> 元素进行混淆。

举例来说,这里有一个 <FancyButton> 组件,可以像这样使用:

<FancyButton>
  Click me! <!-- 插槽内容 -->
</FancyButton>

而 <FancyButton> 的模板是这样的:

<button class="fancy-btn">
  <slot></slot> <!-- 插槽出口 -->
</button>

<slot> 元素是一个插槽出口 (slot outlet),标示了父元素提供的插槽内容 (slot content) 将在那里被渲染。

slots.dbdaf1e8.png

最终渲染出的 DOM 是这样:

<button class="fancy-btn">Click me!</button>

插槽内容可以是任意合法的模板内容,不局限于文本。例如我们可以传入多个元素,甚至是组件:

<FancyButton>
  <span style="color:red">Click me!</span>
  <AwesomeIcon name="plus" />
</FancyButton>

2.2 优点

使用插槽可以让组件更灵活和具有可复用性。因此组件可以用在不同的地方渲染各异的内容,但同时还保证都具有相同的样式

2.3 渲染作用域

插槽内容可以访问到父组件的数据作用域。因为插槽内容本身是在父组件模板中定义的。举例来说:

<span>{{ message }}</span>
<FancyButton>{{ message }}</FancyButton>

这里的两个 {{ message }} 插值表达式渲染的内容都是一样的。

插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:

父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。

2.4 插槽种类

2.4.1 默认插槽

在外部没有提供任何内容的情况下,可以为插槽指定默认内容。比如有这样一个 <SubmitButton> 组件:

<button type="submit">
  <slot></slot>
</button>

如果我们向在父组件提供任何插槽内容是在<button> 内渲染“Submit”,只需要将“Submit”写在 <slot> 标签之间来作为默认内容:

<button type="submit">
  <slot>
    Submit <!-- 默认内容 -->
  </slot>
</button>

现在,当我们在父组件中使用 <SubmitButton> 且没有提供任何插槽内容时:

// 不提供内容
<SubmitButton />
// “Submit”将会被作为默认内容渲染:
<button type="submit">Submit</button>


// 提供了插槽内容:
<SubmitButton>Save</SubmitButton>
// 那么被显式提供的内容会取代默认内容:
<button type="submit">Save</button>

2.4.2 具名插槽

有时在一个组件中包含多个插槽出口是很有用的。举例来说,在一个 <BaseLayout> 组件中,有如下模板:

<div class="container">
  <header>
    <!-- 标题内容放这里 -->
  </header>
  <main>
    <!-- 主要内容放这里 -->
  </main>
  <footer>
    <!-- 底部内容放这里 -->
  </footer>
</div>

对于这种场景,<slot> 元素可以有一个特殊的 attribute name,用来给各个插槽分配唯一的 ID,以确定每一处要渲染的内容:

// 子组件
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>


// 父组件
// 要为具名插槽传入内容,我们需要使用一个含 `v-slot` 指令的 `<template>` 元素,并将目标插槽的名字传给该指令
<BaseLayout>
  <template v-slot:header>
    <!-- header 插槽的内容放这里 -->
  </template>
</BaseLayout>

这类带 name 的插槽被称为具名插槽 (named slots)。没有提供 name 的 <slot> 出口会隐式地命名为“default”。

v-slot 的简写是 #,因此 <template v-slot:header> 可以简写为 <template #header>

named-slots.ebb7b207.png

下面我们给出完整的、向 <BaseLayout> 传递插槽内容的代码,指令均使用的是缩写形式:

<BaseLayout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <template #default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>

// 当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非 `<template>` 节点都被隐式地视为默认插槽的内容。所以上面也可以写成:
<BaseLayout>
  <template #header>
    <h1>Here might be a page title</h1>
  </template>

  <!-- 隐式的默认插槽 -->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template #footer>
    <p>Here's some contact info</p>
  </template>
</BaseLayout>

现在 <template> 元素中的所有内容都将被传递到相应的插槽。最终渲染出的 HTML 如下:

<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

2.4.3 动态插槽名

动态指令参数在 v-slot 上也是有效的,即可以定义下面这样的动态插槽名:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>

  <!-- 缩写为 -->
  <template #[dynamicSlotName]>
    ...
  </template>
</base-layout>

注意这里的表达式和动态指令参数受相同的语法限制

2.4.4 作用域插槽

作用域插槽,允许父组件传递数据到插槽内部,这样子组件可以访问和渲染这些数据。

在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。

可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:

<!-- <MyComponent> 的模板 -->
<div>
  <slot :text="greetingMessage" :count="1"></slot>
</div>

当需要接收插槽 props 时,默认插槽和具名插槽的使用方式有一些小区别。下面我们将先展示默认插槽如何接受 props,通过子组件标签上的 v-slot 指令,直接接收到了一个插槽 props 对象:

<MyComponent v-slot="slotProps">
  {{ slotProps.text }} {{ slotProps.count }}
</MyComponent>

scoped-slots.1c6d5876.svg

v-slot="slotProps" 可以类比这里的函数签名,和函数的参数类似,我们也可以在 v-slot 中使用解构:

<MyComponent v-slot="{ text, count }">
  {{ text }} {{ count }}
</MyComponent>

2.5 插槽小结

2.5.1 插槽作用

  • 插槽允许你将组件的内容分发到其子组件中,以实现灵活的组件复用和自定义布局。
  • 通过插槽,父组件可以向子组件传递内容,这样可以根据具体需求定制组件的外观和行为。
  • 插槽提供了一种强大的方式来创建通用组件,使其更具可定制性。

2.5.2 具名插槽

  • Vue 支持具名插槽,这意味着你可以在组件中定义多个插槽,并为每个插槽取一个名字。
  • 具名插槽允许父组件将内容分发到指定的插槽位置,从而实现更精细的内容控制。
  • 具名插槽通过 slot 属性来定义和使用,可以在子组件中使用 <slot> 元素来展示父组件传递的内容

2.5.3 不同插槽的区别

  • 默认插槽:如果组件没有具名插槽,父组件的内容将插入到默认插槽中。
  • 具名插槽:具名插槽允许你在父组件中将内容分发到特定的插槽位置,以满足不同情况下的布局需求。
  • 作用域插槽:Vue 还提供了作用域插槽,允许父组件传递数据到插槽内部,这样子组件可以访问和渲染这些数据。
  • 匿名插槽:当你不为插槽分配名字时,它被称为匿名插槽,用于默认情况下的内容分发。

三、<template>

当我们想要使用内置指令而不在 DOM 中渲染元素时,<template> 标签可以作为占位符使用。

2.1 <template>介绍

对 <template> 的特殊处理只有在它与以下任一指令一起使用时才会被触发:

  • v-ifv-else-if 或 v-else
  • v-for
  • v-slot

如果这些指令都不存在,那么它将被渲染成一个原生的 <template> 元素。

2.2 <template> 上的 v-if

因为 v-if是一个质量,所以它必须依附在某个元素上。但是如果我们想要切换不止一个元素呢?在这种情况下,我们可以在一个<template> 元素上使用 v-if,这只是一个不可见的包装器元素,最后渲染的结果并不会包含这个 <template> 元素。

v-else 和 v-else-if 也可以在 <template> 上使用。

2.3 <template> 上的 v-for

与模板上的 v-if 类似,你也可以在 <template> 标签上使用 v-for 来渲染一个包含多个元素的块。例如:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

资料来源