重学vue3 - 插槽

213 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第15天,点击查看活动详情

插槽

我们可以使用props来为组件传递不同的数据

但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素

比如某种情况下我们使用组件,希望组件显示的是一个按钮,某种情况下我们使用组件希望显示的是一张图片

我们应该让使用者可以决定某一块区域到底存放什么内容和元素,这个时候就需要使用到插槽

插槽的使用过程其实是抽取共性、预留不同

我们会将共同的元素、内容依然在组件内进行封装

同时会将不同的元素使用slot作为占位,让外部决定到底显示什么样的元素

在封装组件中,使用特殊的元素<slot>就可以为封装组件开启一个插槽

该插槽插入什么内容取决于父组件如何使用

image.png

子组件

<div>
 <!--
	slot元素就是对应的插槽标签
	如果外部传递过来内容的时候,会替换掉slot标签,显示传递过来的内容
	如果外部没有传入对应的内容的时候,会使用slot中编写的预设作为slot的默认值
 -->
  <slot>
    <h2>this is default slot</h2>
  </slot>
</div>

父组件

<!-- 传递具体插槽内容的组件 -->
<Child>
  <button>click me</button>
</Child>

<!-- 没有传递具体插槽内容,即使用插槽默认值的组件  -->
<Child />

具名插槽

如果一个组件中含有多个插槽,那么对应的内容会依次替换对应的插槽

子组件

<template>
	<ul class="nav">
		<li class="left">
			<slot>left</slot>
		</li>
		<li class="center">
			<slot>center</slot>
		</li>
		<li  class="right">
			<slot>right</slot>
		</li>
	</ul>
</template>

父组件

<template>
	<Child>
		<button>上一个</button>
		<div>内容</div>
		<button>下一个</button>
	</Child>
</template>

最终效果

image.png

如果我们希望在具体的插槽中放置不同的内容的时候,可以使用具名插槽

  • 具名插槽顾名思义就是给插槽起一个名字,<slot> 元素有一个特殊的 attribute:name

  • 一个不带 name 的slot,会带有隐含的名字 default

子组件

<template>
<ul class="nav">
  <li class="left">
    <!-- 通过name属性给slot起一个具体的名称 -->
    <!-- 如果不起名称的话,会存在默认名称 即为default -->
    <slot name="left">left</slot>
  </li>
  <li class="center">
    <slot name="center">center</slot>
  </li>
  <li  class="right">
    <slot name="right">right</slot>
  </li>
  </ul>
</template>

父组件

<template>
  <Child>
    <!--
      可以在具体内容外部包裹一层v-slot:[name]
      以表示具体希望被插入到那个slot中
      如果不写,则表示需要将内容插入到默认插槽中

      v-slot:[name] 可以简写为 #[name]
    -->
    <template v-slot:left>
      <button>上一个</button>
    </template>

		<!--
			默认插槽的template可以不写
			即被简写为<div>内容</div>
		-->
    <template v-slot:default>
    <div>内容</div>
    </template>

    <template #right>
    <button>下一个</button>
    </template>
  </Child>
</template>

动态插槽名

所谓动态插槽名,即插槽的名称并不是写死的,而是一个变量

我们可以通过 v-slot:[dynamicSlotName]方式动态绑定一个名称

<template>
	<template #[slotName]>
		<button>
    	 click me
  	</button>
	</template>
</template>

渲染作用域

父级模板里的所有内容都是在父级作用域中编译的

子模板里的所有内容都是在子作用域中编译的

<Child>
  <!--
		虽然插槽中的内容,最后是被渲染到子组件中的
		但是因为存在渲染作用域的关系,
		这里的msg只能是父组件中定义的变量
		是无法读取到子组件中对应的变量
	-->
 <div>{{ msg }}</div>
</Child>

作用域插槽

但是有时候我们希望插槽可以访问到子组件中的内容, 这个Vue给我们提供了作用域插槽

父组件

<template>
	<div>
		<Child>
			<!--
				所有子组件传递给父组件插槽部分的内容都会被封装为一个对象
				所以可以在获取对应值的时候,直接使用解构语法
			-->
			<template #default="{username, age}">
				<p>{{ username }}</p>
				<p>{{ age }}</p>
			</template>
		</Child>
	</div>
</template>

<script>
import Child from './components/Child';

export default {
	components: { Child }
}
</script>

子组件

<template>
	<div>
		<!-- 将父组件中需要使用的子组件中的数据传递给插槽 -->
		<slot :username="name" :age="age">
			default value
		</slot>
	</div>
</template>

<script>
export default {
	data() {
		return {
			name: 'Klaus',
			age: 23
		}
	}
}
</script>

独占默认插槽

如果我们的插槽是默认插槽default, 那么在使用的时候 v-slot:default="slotProps"可以简写为v-slot="slotProps"

并且如果我们的插槽只有默认插槽时,组件的标签可以被当做插槽的模板来使用,这样,我们就可以将 v-slot 直接用在组件上

<Cpn #default="{ msg }">
	<span>{{ msg }}</span>
</Cpn>

但是,如果我们有默认插槽和具名插槽,那么按照完整的template来编写, 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法