讲清楚vue的插槽

434 阅读2分钟

1 插槽

组件封装的一把利器

1.1 是什么

插槽,也就是slot,它相当于一个占位符,允许父组件向子组件插入一些内容,这些内容在子组件中的位置就由slot来决定。

实用场景,根据业务对组件库中的组件进行二次封装,一般组件库中提供的组件都会留下可灵活插入内容的插槽,可以让使用者自定义某些内容。

1.2 怎么用

  • 匿名插槽

匿名插槽就是一个没有name属性的插槽。父组件中除了具名包裹内容外,其他内容全部插入到匿名插槽中。

匿名插槽虽然没有name属性,但出口会带有一个隐含的名字“default”。单独使用匿名插槽时,父组件中可以不标注default,但使用多个插槽时,最好以名字区分插槽。

<!-- 子组件 -->
<template>
  <div class="child">
    <h3>子组件:匿名插槽</h3>
    <slot></slot>
  </div>
</template>

<!-- 父组件 -->
<template>  
  <div class="parent">    
    <child>      
      <template>        
        <p>插入匿名插槽</p>      
      </template>    
    </child>  
  </div>
</template>
  • 具名插槽

具名插槽就是一个有name属性的插槽。

<!-- 子组件 -->
<template>
  <div class="child">
    <h3>子组件:具名插槽</h3>
    <slot name="child"></slot>
  </div>
</template>

<!-- 父组件1:vue2.6之后的写法 -->
<template>  
  <div class="parent">    
    <child>      
      <template v-slot:child>         
        <p>插入具名插槽</p>      
      </template> 
    </child>  
  </div>
</template>
  
<!-- 父组件2:v-slot的简写 -->
<template>  
  <div class="parent">    
    <child>      
      <template #child>         
        <p>插入具名插槽</p>      
      </template> 
    </child>  
  </div>
</template>
  
<!-- 父组件3:vue2.6之前的写法 -->
<template>  
  <div class="parent">    
    <child>      
      <p slot="child">插入具名插槽</p>      
    </child>  
  </div>
</template>

v-slot和slot的用法还是有区别的,v-slot只能用于template和组件,而slot属性可以加在任意标签上。

  • 作用域插槽

只有匿名插槽和具名插槽是远远不够的,这两种插槽仅仅只能把Dom插入到封装好的组件中,而不能获取组件中的数据。

我们的业务需求中需要定制或限制数据展示、组件内部提供一个函数方法去让外部调用修改。这就需要我们在父组件中操作这些数据,要获取子组件中的数据,就要使用作用域插槽。

<!-- 子组件 -->
<template>
  <div class="child">
    <h3>子组件:作用域插槽</h3>
    <slot name="user" :user="user"></slot>
  </div>
</template>
<script>
export default {
  data() {
    return {
      user: {
        name: 'zhunny',
        age: 26
      }
    }
  }
}
</script>

<!-- 父组件1 vue2.6之后的用法 -->
<template>  
  <div class="parent">    
    <child>      
      <template #user="{ user }">        
        <p>hi,my name is {{ user.name }}, i am {{age}} years old</p>
      </template>    
    </child>  
  </div>
</template>
  
<!-- 父组件2 vue2.6之前的用法 -->
<template>  
  <div class="parent">    
    <child>      
      <template slot="user" slot-scope="user">        
        <p>hi,my name is {{ user.name }}, i am {{age}} years old</p>
      </template>    
    </child>  
  </div>
</template>

作用域插槽使用v-bind绑定需要暴露出去的数据。

1.3 JSX中使用插槽

JSX中是没有v-slot指令的

假设父组件和子组件全部使用JSX来实现:

  • 匿名插槽和具名插槽
 <!-- 父组件使用elementui的弹窗组件,省略了不必要的属性 -->
 render() {
    return (
      <el-dialog title="弹框标题">
        {/*这里就是默认插槽*/}
        <div>这里是弹框内容</div>
        {/*这里就是具名插槽*/}
        <template slot="footer">
          <el-button>确定</el-button>
          <el-button>取消</el-button>
        </template>
      </el-dialog>
    )
  }

 <!-- 子组件即Dialog的内部实现 -->
 render() {
    return (
      <div>
        {/**自定义匿名插槽*/}
        {this.$slots.default}
        {/**自定义具名插槽*/}
        <div>{this.$slots.footer}</div>
      </div>
    )
  }

  • 作用域插槽
 <!-- 父组件使用elementui的Table组件,自定义列表和表头 -->
 render() {
    return (
      <el-table data="this.data">
        {/*这里就是作用域插槽*/}
        <el-table-column
          scopedSlots={{
            default: ({ row, $index }) => {
              return <el-button
              size="mini"
              type="danger"
              @click="handleDelete(scope.$index, scope.row)">
              > 删除
              </el-button>
            },
            header: () => {
              return <span style="color:red">操作</span>
            }
          }}
        ></el-table-column>
      </el-table>
    )
  }

 <!-- 子组件即Table-Column的内部实现,表内容与title同理 -->
render() {
    const { data } = this
    // 获取标题作用域插槽
    const titleSlot = this.$scopedSlots.title
    return (
      <div>
        {/** 如果有标题插槽,则使用标题插槽,否则使用默认标题 */}
        {titleSlot ? titleSlot(data) : <span>{data.title}</span>}
      </div>
    )
  }

1.4 比较$scopeSlots和$slots

vue2.6之后,$scopeSlots数组中包含了所有类型的插槽,数组中的元素的类型是一个函数,通过调用函数生成一个VNode,渲染到页面上,函数的入参是需要传递的数据。

$slots中只有匿名插槽和具名插槽,它的元素类型就只是一个VNode。

但是有情况是特殊的,存在同名的作用域插槽和具名插槽:

<!-- 父组件 -->
<template>  
  <div class="parent">  
    <child>     
      {/** 插入匿名插槽 */}
      插入匿名插槽
      {/** 插入名为default的作用域插槽 */}
      <template #default="{ test }">        
        hi,{{ test }}
      </template>    
    </child>  
  </div>
</template>
  
<!-- 子组件 -->
render() {
    return (<div>
      <p>{this.$scopedSlots.default({ test: this.test })}</p>
      <p>{this.$slots.default}</p>
    </div>)
  },
  data() {
    return {
      test: '测试'
    }
  }

这种情况下,$scopeSlot中的default为作用域插槽,$slots中的default为匿名插槽。

<!-- 父组件 -->
<template>  
  <div class="parent">  
    <child>     
      {/** 插入匿名插槽 */}
      插入匿名插槽   
    </child>  
  </div>
</template>
  
<!-- 子组件 -->
render() {
    return (<div>
      <p>{this.$scopedSlots.default({ test: this.test })}</p>
      <p>{this.$slots.default}</p>
    </div>)
  },
  data() {
    return {
      test: '测试'
    }
  }

而不存在同名的具名插槽和作用插槽时,$scopeSlot中的default和$slots中的default为同一个插槽。

<!-- 父组件 -->
<template>  
  <div class="parent">  
    <child>     
      {/** 插入名为default的作用域插槽 */}
      <template #default="{ test }">        
        hi,{{ test }}
      </template>    
    </child>  
  </div>
</template>
  
<!-- 子组件 -->
render() {
    return (<div>
      <p>{this.$scopedSlots.default({ test: this.test })}</p>
      <p>{this.$slots.default}</p>
    </div>)
  },
  data() {
    return {
      test: '测试'
    }
  }

1.5 参考资料

  1. 学会使用Vue JSX,一车老干妈都是你的:juejin.cn/post/684668…
  2. Vue组件封装利器——slot插槽:juejin.cn/post/685823…