vue.js的slot插槽-你没懂是我的错

277 阅读3分钟

为什么我突然间想写插槽相关的呢?是因为刚开始看vue.js插槽教程写的实在是晦涩难懂,还有一个原因是这2年插槽在我们项目中用到非常多,尤其是作用域插槽,作用很大!!!

1.slot是什么?在哪里用到?什么时候用到?作用?

slot是什么

插槽可以说是占了一个坑(通过标记),然后插进去一些东西 插入东西去哪里 把东西插入到组件里面 插入什么东西以及场景 当小a和小b都要共同用一个组件,但是小A有一些独有的东西要在这个组件里面使用,组件会为了小A预留插入独有东西的空间 作用 通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理
如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情
通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用

2.插槽分类

场景:假设有两个弹框,都有这相同的样式,title与详情(这时我们肯定会抽出共同的组件)

App.vue

<template>
  <div id="app">
    <Test1/>
    -------------------上面test1,下面test2-------
    <Test2/>
  </div>
</template>

<script>
import Test1 from './components/Test1.vue'
import Test2 from './components/Test2.vue'

export default {
  name: 'App',
  components: {
    Test1,
    Test2,
  }
}
</script>

dialog组件commonSlot.vue

<template>
  <div>
     <div>标题:{{title}}</div>
     <div>文案:{{desc}}</div>
  </div>
</template>
<script>
export default {
  props: {
    title: {
      type: String,
      default: '',
    },
    desc: {
      type: String,
      default: '',
    },
  }
}
</script>

Test1.vue

<template>
  <div class="hello">
    <Dialog
      title="test1"
      desc="test1描述"
    />
  </div>
</template>
<script>
import Dialog from './commonSlot.vue'
export default {
  name: 'Test1',
  components: {
    Dialog,
  },
}
</script>

Test2.vue

<template>
  <div class="hello">
    <Dialog
      title="test2"
      desc="test2描述"
    />
  </div>
</template>
<script>
import Dialog from './commonSlot.vue'
export default {
  name: 'Test2',
  components: {
    Dialog,
  },
}
</script>

1636439991(1).png

上面是一个很简单的组件commonSlot.vue,但是实际的弹框页面信息会很多,这里我做的是粗略版本哦~

2.1 普通插槽

场景:第一个弹框的标题下给我加个提示用户的tips吧

这个时候,我几种选择可以实现这个,1.直接在组件里面加,然后通过组件父级传入参数,来决定显示与否 2.是利用插槽.

但是想想如果直接在组件里面改的话,我每次修改文案的话,都要修改组件,是不是有点小麻烦,毕竟组件这个东西,不是你一个人在用对不对?而且加的东西很多的话会很乱,自由度也不高

Test1.vue

    <Dialog
      title="test1"
      desc="test1描述"
    >
      <span style="color:orange;font-size:12px">tips: 摊牌了!小橘子是个大美人</span> // 新加
    </Dialog>

commonSlot.vue

  <div>
     <div>标题:{{title}}</div>
     <slot></slot>  // 新加
     <div>文案:{{desc}}</div>
  </div>

注意:commonSlot.vue组件里面的 标签里面的位置,可不是乱加的哦,需要在哪里插入东西,就写在哪里,而Test1.vue里面的添加的东西必须写在组件的开标签和闭标签之间~

1636441071(1).png

2.2 具名标签

场景:第一个弹框的标题下给我加个提示用户的tips吧,给我在第二个弹框的描述下方加个取消按钮

接上: 如果我在Test2.vue直接写入

    <Dialog
      title="test2"
      desc="test2描述"
    >  
      <button>取消</button>
    </Dialog>

1636441336(1).png

上面必然不满足我们的需求,因为要求是-第二个弹框的描述下方加个取消按钮,所以这个时候我们的具名插槽上线了~

所以,

commonSlot.vue

 <div>标题:{{title}}</div>
 <slot name="tips"></slot>
 <div>文案:{{desc}}</div>
 <slot name="btn"></slot>

Test1.vue

<Dialog
  title="test1"
  desc="test1描述"
>
  <template #tips>
    <span style="color:orange;font-size:12px">tips: 摊牌了!小橘子是个大美人</span>
  </template>
</Dialog>

Test2.vue

<Dialog
  title="test2"
  desc="test2描述"
>  
  <template #btn>
    <button>取消</button>
  </template>
</Dialog>

#tips 是 v-slot:tips 的缩写形式,所以具名插槽什么时候用呢?--当我们需要多个插槽时

2.3 作用域插槽

先看看这个作用域插槽简单的案例.

场景:两个相同的列表,并且给我显示对应的单位

commonSlot.vue

<template>
  <div>
    <ul>
      <slot 
        v-for="item in list"
        :item="item"
        :name="`slot${item.prop}`"
      >
        [{{item.title}} {{ item.text}}]
      </slot>
    </ul>
  </div>
</template>
<script>
export default {
  props: {
    list: {
      type: String,
      default: '',
    },
  },
}
</script>

Test2.vue

<template>
  <div class="hello">
    <Dialog
      :list="list"
    />
  </div>
</template>
<script>
import Dialog from './commonSlot.vue'
export default {
  name: 'Test2',
  components: {
    Dialog,
  },
  data() {
    return {
        list: [
        {
          title: '姓名',
          text: '小橘子',
          prop: 'name',
        },
        {
          title: '年龄',
          text: '18',
          prop: 'age',
        },
        {
          title: '职业',
          text: '前端',
          prop: 'occupation',
        },
      ],
    }
  },
}
</script>

Test1.vue

<template>
  <div class="hello">
    <Dialog
      :list="list"
    >
    <template #slotsalary="props">
      <span style="color:red">
        [{{props.item.title}} {{ props.item.text}}万]
      </span>
    </template>
    </Dialog>
  </div>
</template>
<script>
import Dialog from './commonSlot.vue'
export default {
  name: 'Test1',
  data() {
    return {
      list: [
        {
          title: '姓名',
          text: '小橘子',
          prop: 'name',
        },
        {
          title: '月薪',
          text: '1',
          prop: 'salary',
        },
      ],
    }
  },
  components: {
    Dialog,
  },
}
</script>

Test1.vue可以进行优化解构:

   <template #slotsalary="props">
      <span style="color:red">
        [{{props.item.title}} {{ props.item.text}}万]
      </span>
    </template>

=>

    <template #slotsalary="{ item: { title, text }}">
      <span style="color:red">
        [{{title}} {{ text}}万]
      </span>
    </template>

1636442791(1).png

其实这种场景是非常非常常见的,也就是说父组件应该拿到子组件的数据,自己想怎么渲染就怎么渲染.看看Test2.vue就是没有去利用插槽,直接子组件里面是什么就是什么,但是Test1.vue必须要用户知道这个月薪的单位是什么,所以必须拿到子组件对应的数据.

所以作用域插槽是用在父组件需要子组件的数据的时候~

2.4 作用域插槽题外话

我每天都能用到作用域插槽,为什么呢?因为我们项目的table组件是对(element ui的table)进行二次封装,使我们的表头以及对应的表体都是直接用json配置出来,当有表头和数据显示有额外的处理,都是通过具名插槽+作用域插槽的,因为table的数据都是来自于一个通用的接口

<el-table
  ref="compTable"
  v-loading="tableLoading"
  v-bind="$attrs"
  :max-height="tableMaxHeight"
  :data="tableData"
  v-on="$listeners"
>
  <!--  type内置类型列  -->
  <el-table-column
    v-if="isSelect"
    type="selection"
    align="center"
    :selectable="selectable"
    :reserve-selection="reserveSelection"
  />
  <template v-for="(col, index) in column">
    <!--  普通列  -->
    <el-table-column
      :key="index"
      :prop="col.prop"
      :label="col.label"
      :index="col.index"
      :column-key="col.columnKey"
      :width="col.width"
      :min-width="col.minWidth"
      :fixed="col.fixed"
      :render-header="col.renderHeader"
      :sortable="col.sortable || false"
      :sort-method="col.sortMethod"
      :sort-by="col.sortBy"
      :sort-orders="col.sortOrders"
      :resizable="typeof col.resizable === 'undefined' ? true : col.resizable"
      :formatter="col.formatter"
      :show-overflow-tooltip="col.showOverflowTooltip || false"
      :align="col.align || 'center'"
      :header-align="col.headerAlign || col.align || 'center'"
      :class-name="col.className"
      :label-class-name="col.labelClassName"
      :selectable="col.selectable"
      :reserve-selection="col.reserveSelection || false"
      :filters="col.filters"
      :filter-placement="col.filterPlacement"
      :filter-multiple="col.filterMultiple"
      :filter-method="col.filterMethod"
      :filtered-value="col.filteredValue"
    >
      <!--  列头  -->
      <template
        #header="scope"
      >
        <!--  表头插槽  -->
        <slot :name="`${col.prop}Head`" :scope="scope">
          {{ scope.column.label }}
        </slot>
      </template>

      <!--  内容  -->
      <template
        v-slot:default="scope"
      >
        <!--  常规的按钮  -->
        <div v-if="col.btnData">
          <button-comp
            v-if="col.btnData.viewBtn"
            :button-item="col.btnData.viewBtn"
            @clickFunc="getRowData(scope, index, 'viewBtn', 'viewData')"
          />
          <button-comp
            v-if="col.btnData.editBtn"
            :button-item="col.btnData.editBtn"
            @clickFunc="getRowData(scope, index, 'editBtn', 'editData')"
          />

          <!-- 编辑按钮后,删除按钮前的插槽 -->
          <slot name="btnSlotMiddle" :scope="scope" />

          <button-comp
            v-if="col.btnData.deleteBtn"
            :button-item="col.btnData.deleteBtn"
            @clickFunc="deleteRow(scope, index)"
          />

          <!--  更多按钮插槽  -->
          <slot name="btnSlotLast" :scope="scope" />
        </div>

        <!--  表格内容插槽  -->
        <slot v-else :name="`${col.prop}`" :scope="scope">
          {{ scope.row[col.prop] }}
        </slot>

      </template>
    </el-table-column>
  </template>
</el-table>

3.我的公众号

觉得自己并没有代码天赋,只能说希望自己更加勤奋吧,最近在写一些关于在前端行业摸爬滚打还有生活中的趣事来让自己变得更加快乐,追求生活和工作中的进步.

公众号:程序媛爱唠嗑
欢迎关注,一起唠嗑

c79fa8967887df6527a162bd85cae3a.jpg