vue -- 插槽的使用方法

115 阅读7分钟

前言

今天是6月更文的第17天,大家要坚持下去啊,这次终于忍不住要把以前的积压的库存掏出来了😂

俗话说得好:温故而知新,每一次的回顾都是对知识的不断深化,

虽然我学了就忘,忘了又学,感觉自己跟金鱼一样只有7秒的记忆 🤣

但是我相信每一次的学习都会有所收获,找到新的记忆点,整理文章的过程也是对知识的回顾和总结。

今天就来看看久违的的知识点 -- 插槽!!!

什么是插槽

Vue 实现了一套内容分发的 API, 将 <slot> 元素作为承载分发内容的出口。

slot 可以让你在组件内添加内容 空间,在子组件中 通过 <slot>  为父组件提供的占位符,父组件可以在这个占位符中设置任何内容 (html、组件等内容)

// 名为 mybutton 的子组件
  <div class= 'button'>
      <button></button>
      <slot></slot>   //在子组件中 slot 可以放在任意位置。(这个位置就是父组件添加内容的显示位置)
  </div>

在父组件中引用子组件,子组件中 若不设置 slot 则父组件在子组件中添加的内容无法在页面上渲染的

// 在父组件引用子组件 mybutton
<mybutton> 
    啦啦啦
</mybutton>

在子组件中  任何位置都可以插入 slot 作为父组件的占位符,该位置是父组件添加内容的展示区域。

插槽的默认值

有时为一个插槽设置具体的默认内容是很有用的,它只会在没有提供内容时被渲染。

插槽的默认值也被称为 后备内容

插槽可以设置默认值,当父组件没有设置任何内容时,slot 中设置的默认内容将会被渲染。

// 子组件 中的后备内容
<button type="submit">
  <slot>Submit</slot>
</button>

此时  Submit 作为 子组件的 默认值,若父组件中提供了新的内容, Submit 将会被新的内容所取代。

编译作用域

插槽中的编译作用域是指在组件模板中使用的插槽内容的作用域。

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

在编译过程中,Vue 会将插槽内容编译成一个函数,并将其作为参数传递给父组件。这个函数的作用域是父组件的作用域,因此插槽内容可以访问父组件中的数据和方法。

<!-- 子组件 -->
<template>
  <div>
    <slot></slot>
  </div>
</template>

<!-- 父组件 -->
<template>
  <div>
    <child-component>
      <p>Hello, world!</p>
    </child-component>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

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

父组件中通过在 标签内部添加内容来向子组件传递内容。这里传递的内容是一个

标签。

当 Vue 编译这个模板时,它会将子组件中的 标签编译成一个函数,并将这个函数作为参数传递给父组件。这个函数的作用域是父组件的作用域,所以这个函数可以访问父组件中的属性和方法。

还有一点比较难理解的是:

插槽可以访问相同实例的作用域,但不能直接访问子组件内部的变量。

这里的“相同实例”指的是同一个 Vue 实例。在 Vue 组件中,父组件和子组件是同一个 Vue 实例,因此它们共享相同的作用域。

当子组件中使用插槽时,插槽的编译作用域是在父组件的作用域中定义的,但是插槽不能直接访问子组件内部的变量,是因为这些变量是在子组件的作用域中定义的,而插槽的编译作用域是在父组件的作用域中定义的,所以无法直接访问子组件内部的变量。

如果需要在插槽中访问子组件内部的数据,可以通过 v-slot 将数据作为 props 传递给插槽来实现

<!-- 子组件 -->
<template>
  <div>
    <slot :url="url"></slot>
  </div>
</template>

<script>
export default {
  data() {
    return {
      url: 'https://www.example.com'
    }
  }
}
</script>

<!-- 父组件 -->
<template>
  <div>
    <child-component>
      <template v-slot="{ url }">
        <a :href="url">Link</a>
      </template>
    </child-component>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue'

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

匿名插槽

没有为插槽设置指定的名称, 则为匿名插槽。

通过slot标签可以添加匿名插槽,在使用组件的时候,组件中的内容会填充到所有匿名插槽的位置,在组件中一般只有一个匿名插槽

如果没有项插槽中添加内容则会显示插槽的默认内容,如果传入了则会进行覆盖

// 名为 mybutton 的子组件
  <div class= 'button'>
      <button></button>
      <slot></slot>       //slot 可以放在任意位置。(这个位置就是父组件添加内容的显示位置)
  </div>

具名插槽

当子组件中设置多个 slot 时,通过命名来区分插入的位置,在一个组件中可以有多个具名插槽

<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

// 子组件 myContent
<div> 
    <slot name="header"> 这里是 header </slot>
    <slot> 这里是剩下的部分 </slot>
    <slot name="footer"> 这里是 footer </slot>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default”。

在父组件中 通过 v-slot:name   或者使用  具名插槽  #name  的缩写方式将内容插入到相应的位置:

v-slot: 可以被重写成 # 替换

// 在父组件中引用子组件  myContent
<myContent>
	<template v-slot:header >插入的header内容</template>
     <p>猜一猜这些</p>
     <p>内容会在哪呢</p>
    <template #footer>插入的footer内容</template>
</myContent>

<template> 元素中的所有内容都将会被传入相应的插槽。而<template> 中所有没有被包裹在 v-slot 中的内容都会在默认插槽的位置展示。

具名插槽的 v-slot 只能在  <template> 上添加

当然也可以通过 slot 属性直接使用,但是必须与子组件中的名称保持一致否则无法插入对应的内容

<myContent>
   <p slot="header" >插入的header内容</p>
   <p>猜一猜这些</p>
   <p>内容会在哪呢</p>
  <p slot="footer">插入的footer内容</p>
</myContent>

作用域插槽

作用域插槽  让父组件能够使用子组件插槽处的数据

作用域插槽的实现

  • 在子组件的slot上动态绑定一个值 ( :key='value')
  • 作用域插槽需要设置name属性,在父组件通过v-slot:name = ‘values ’的方式将这个值赋值给 values
  • 最后通过{{ values.key }}的方式获取数据
// 子组件 myContent
<div> 
    <!-- 子组件中的数据可能来源于接口请求, 父组件无法直接获取子组件中的数据, v-slot 作用域插槽变为父组件提供了一种获取数据信息的方式  -->
    <slot name="header" v-bind:user="userName"> 用户名:{{userName}}</slot>
    <slot> 这里是剩下的部分 </slot>
    <slot name="footer" :phone="phone"> 联系电话:{{phone}} </slot>
</div>

<script>
    new Vue({
      data:{
        userName: '啦啦啦'
        phone: 123456789,
      }
    })	
</script>

绑定在 <slot> 元素上的 atribute 被称为 插槽 prop

在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的 插槽 prop 的名字,父组件通过 插槽 prop 使用子组件中的数据。

// 在父组件中应用子组件 myContent
<myContent>
    <template v-slot:header="headerProp">{{headerProp.userName}}</template>
    <template v-slot:default="defaultProp">{{defaultProp}}</template>
    <template v-slot:footer="footerProp">{{footerProp.phone}}</template>
</myContent>

独占默认插槽的缩写语法

当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。

此时 v-slot 可以直接应用在组件上。

 
// 子组件 mybutton
<div class= 'button'>
    <button></button>
    <slot :btn="btnName">{{btnName}}</slot>       
</div> 


// 父组件引用时 使用默认插槽 此时 v-slot 可以直接写在组件上 (默认插槽的缩写语法)
<mybutton v-slot:default="defalutProp"> 
  按钮名称:{{defalutProp.btn}}
</mybutton>


// v-slot:default  可以被缩写为 v-slot
<mybutton v-slot="defalutProp"> 
  按钮名称:{{defalutProp.btn}}
</mybutton>

只要出现多个插槽,需要为所有的插槽使用完整的基于 <template> 的语法,

切记不能将默认插槽的缩写语法(直接在组件上直接使用 v-slot)和 具名插槽(template 上使用 v-slot)混用,因为它会导致作用域不明确。

<!-- 无效,会导致警告 -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
  <template v-slot:other="otherSlotProps">
    slotProps is NOT available here
  </template>
</current-user>

解构插槽prop

作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:

v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。,可以通过解构赋值的方式,解构插槽的 prop

function (slotProps) {
  // 插槽内容
}

在 v-slot 中解构对应的参数,(解构赋值)

<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>

当插槽提供了多个 prop 的时候,可以为解构的 prop 重命名,例如将 user 重命名为 person

<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>

为解构设置默认值,用于插槽 prop 是 undefined 的情形:

<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

总结

本章内容最难理解的就是编译作用域,我自己看的也是一知半解,有空我也得再研究研究, 希望评论区能有大佬来讲讲 😂