插槽

42 阅读3分钟

概述

插槽是一种将父组件中的内容传递到子组件中的机制,可以让子组件定义一些可变的内容,从而增强组建的灵活性和复用性。

正常情况下,组件的开闭合标签中的内容是无法直接渲染出来的。例如有一个 Child 组件,我们直接在它的开闭合标签中添加一些内容:

<Child>
 <p>Hello, world!</p>
</Child>

会发现 Hello, world!在无法在页面中正常渲染,这时候就需要使用插槽 <slot></slot>

子组件:

<template>
  <div class="child-container">
    <!-- 放置插槽 -->
    <slot></slot>
  </div>
</template>

父组件:

<template>
  <Child>
    <p>Hello, world!</p>
  </Child>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
};
</script>

在页面渲染的时候就会将父组件中的 <p>Hello, world!</p> 顶替掉 子组件中 <slot></slot> 的位置。

注意:

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

(在使用插槽的时候,被插入的内容是无法访问到组件内部实例的方法或属性的。除了 作用域插槽。)

插槽

默认插槽

默认插槽是没有名字的插槽,也称为 匿名插槽

默认插槽是最常用的一种插槽类型,将父组件中的内容会被自动插入到子组件的默认插槽中。

父组件:

<template>
  <Child> Hello, world! </Child>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
};
</script>

子组件:

<template>
  <div class="child-container">
    <p>
      <!-- 默认插槽 -->
      <slot></slot>
    </p>
  </div>
</template>

Hello, world! 会被自动被分配到子组件的 <slot></slot>

具名插槽

具名插槽就是有名字的插槽,它的作用的将父组件中的内容插入到子组件中指定的插槽位置。

子组件:

  • <slot></slot> 添加一个 name 属性来设置名称。
<template>
  <div class="child-container">
    <p>
      <!-- 具名插槽 -->
      <slot name="text"></slot>
    </p>
  </div>
</template>

父组件:

  • 使用 v-slot 指令,例如:v-slot:text ,指定内容插入到名为 text<slot></slot> 中。
<template>
  <Child> 
    <!--指定插入到名字为 text 的插槽中-->
    <template v-slot:text>
      Hello, world!
    </template>
  </Child>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
};
</script>
  • v-slot 指令还可以简写成 #,例如:v-slot:text 可以写成 #text
<Child>
    <template #text> 
        Hello, world! 
    </template> 
</Child>

作用域插槽

作用域插槽就是让插槽内容可以访问到子组件中的数据。

子组件:

<template>
  <div class="child-container">
    <p>
      <!-- <slot> 标签中除了 name 属性以外,其他挂载的属性都会作为数据并用一个对象包裹着传递出去 -->
      <slot name="text" msg="Hello,World" :count="count"></slot>
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    };
  }
};
</script>
  • <slot></slot> 中,除了 name 属性以外,其他属性都会被合成一个对象传出去。

父组件:

<template>
  <Child> 
    <!-- 访问作用域插槽 -->
    <template v-slot:text="data">
      <!-- 子组件传递的数据 -->
      <p>文字:{{ data.msg }}</p>
      <p>计数:{{ data.count }}</p>
    </template>
  </Child>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
};
</script>
  • 注意,在 v-slot 指令外层无法访问到作用域插槽的数据。例如:
<template> 
    <Child> 
        <!-- 访问作用域插槽 --> 
        <template v-slot:text="data"> 
            <!-- 子组件传递的数据 --> 
            <p>文字:{{ data.msg }}</p> 
            <p>计数:{{ data.count }}</p> 
        </template>
        <!-- 作用域外访问 -->
        {{ data.msg }}
    </Child> 
</template>
  • <template></template> 外面的 data.msg 指向的是当前组件实例的数据对象。

默认作用域插槽

默认插槽内容使用子组件的数据。

父组件:

<template>
  <Child> 
    <!-- 访问作用域插槽 -->
    <template v-slot="data">
      <!-- 子组件传递的数据 -->
      <p>文字:{{ data.msg }}</p>
      <p>计数:{{ data.count }}</p>
    </template>
  </Child>
</template>

默认插槽其实会被隐式命名为 default,也就是不指定插槽名称时会自动使用 default 命名。

所以也可以通过 v-slot:default 指向默认插槽。

<template>
  <Child> 
    <!-- 访问作用域插槽 -->
    <template v-slot:default="data">
      <!-- 子组件传递的数据 -->
      <p>文字:{{ data.msg }}</p>
      <p>计数:{{ data.count }}</p>
    </template>
  </Child>
</template>

还可以通过 # 简写形式成 #default

<template>
  <Child> 
    <!-- 访问作用域插槽 -->
    <template #default="data">
      <!-- 子组件传递的数据 -->
      <p>文字:{{ data.msg }}</p>
      <p>计数:{{ data.count }}</p>
    </template>
  </Child>
</template>

解构插槽 Prop

作用域插槽接收过来的数据支持解构语法。

<template>
    <Child> 
        <!-- 访问作用域插槽 --> 
        <template #default="{ msg, count }"> 
            <!-- 子组件传递的数据 --> 
            <p>文字:{{ msg }}</p> 
                <p>计数:{{ count }}</p> 
        </template>
    </Child> 
</template>

动态插槽

动态指令参数也可以用在 v-slot 指令上。

<template>
  <Child> 
    <!-- 访问作用域插槽 -->
    <template #[text]="data">
      <!-- 子组件传递的数据 -->
      <p>文字:{{ data.msg }}</p>
      <p>计数:{{ data.count }}</p>
    </template>
  </Child>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
  data () {
    return {
      text: "text"
    }
  }
};
</script>

重复的插槽内容

一个组件定义了多个同名的插槽,那么在使用该组件时,每个同名的插槽都会渲染对应的内容。就有可能导致一份内容渲染多次。

子组件:

  • 定义两个 <slot name="text"></slot>
<template>
  <div class="child-container">
    <p>
      <slot name="text"></slot>
    </p>
    <p>
      <slot name="text"></slot>
    </p>
  </div>
</template>

父组件:

<template>
  <Child> 
    <template #text>
      Hello,World!
    </template>
  </Child>
</template>

<script>
import Child from "@/components/Child.vue";
export default {
  components: {
    Child,
  },
  data () {
    return {
      text: "text"
    }
  }
};
</script>
  • 只插入了一个 Hello,World! ,但是页面上会出现两个