slot插槽——Vue组件封装利器

5,548 阅读9分钟

组件化是Vue中非常核心的概念,如果想要组件化,那必须要对组件进行封装。

而想要封装组件,那你一定要了解slot才能更好进行封装。

1.什么是插槽?

什么是插槽?请让我用一张图解释一下

概述一下,就是在对已经封装完的组件中插入自己想要定义的不同组件。

说白了,就是封装的组件中插入子组件,而子组件可以根据自己需求去定义。

而slot插槽有三种不同类型的插槽,分别为匿名插槽、具名插槽、作用域插槽。

2.匿名插槽

下面一段代码,教你快速使用匿名插槽

<!--组件调用页面-->
<template>
  <div class="parent">
    <child>
      <template>
        <p>插入匿名插槽</p>
      </template>
    </child>
  </div>
</template>

<script>
import Child from '@/components/Child'
export default {
  components: {
    Child
  }
}
</script>

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

<style scoped>
  .child {
    background#fbd4fc;
  }
</style>

从代码上看

其实就是在封装组件中,加 slot 标签

在要使用的标签上,使用 template 标签,再插入自己想要的组件。

当然,匿名插槽也可以叫默认插槽,这都是别名,不同的叫法。

3.具名插槽

先来看看具名插槽是怎么使用

<!--组件调用页面-->
<template>
  <div class="parent">
    <child>
      <template v-slot:child>
        <p>插入具名插槽</p>
      </template>
    </child>
  </div>
</template>

<script>
import Child from '@/components/Child'
export default {
  components: {
    Child
  }
}
</script>
<!--封装的组件-->
<template>
  <div class="child">
    <h3>子组件:具名插槽</h3>
    <slot name="child"></slot>
  </div>
</template>

<style scoped>
  .child {
    background#b2fffc;
  }
</style>

通过代码可以很容看出

其实就是slot组件上多一个name属性

以及template模板上多了一个v-slot

这样就可以快速“对号入座”

⚠️不过要注意:本文代码基于 vue2.6.0+ 版本

看到这里,或许有小伙伴,有个困惑,具名插槽和默认插槽什么异同呢?

官方文档给出的解释: 下面我提供两个示例,大家一看就能懂

<!--组件调用页面-->
<template>
  <div class="parent">
    <child>
      <template>
        <p>如果不使用v-slot:就默认插入匿名插槽</p>
      </template>
    </child>
  </div>
</template>

<script>
import Child from '@/components/Child'
export default {
  components: {
    Child
  }
}
</script>
<!--封装的组件-->
<template>
  <div class="child">
    <header>
      <h3>头部div</h3>
      <slot name="header"></slot>
    </header>
    <div class="content">
      <h3>中间div</h3>
      <!-- 等价于<slot></slot> -->
      <slot name="default"></slot>
    </div>
    <footer>
      <h3>尾部div</h3>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<style scoped>
  header {
    background#a0c0ff;
  }
  .content {
    background#f8f59a;
  }
  footer {
    background#ffdfdf;
  }
</style>

当然了,v-slot指令可以用#代替

<!--组件调用页面-->
<template>
  <div class="parent">
    <child>
      <template #header>
        <p>插入header</p>
      </template>
      <template>
        <p>插入中间div</p>
      </template>
      <template #footer>
        <p>插入footer</p>
      </template>
    </child>
  </div>
</template>

<script>
import Child from '@/components/Child'
export default {
  components: {
    Child
  }
}
</script>
<!--封装的组件-->
<template>
  <div class="child">
    <header>
      <h3>头部div</h3>
      <slot name="header"></slot>
    </header>
    <div class="content">
      <h3>中间div</h3>
      <!-- 等价于<slot></slot> -->
      <slot name="default"></slot>
    </div>
    <footer>
      <h3>尾部div</h3>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<style scoped>
  header {
    background#a0c0ff;
  }
  .content {
    background#f8f59a;
  }
  footer {
    background#ffdfdf;
  }
</style>

4.作用域插槽

知道了匿名插槽和具名插槽是远远不够!

因为这两种插槽仅仅只能能把Dom插入到封装好的组件中,

而不能获取组件中的数据,这样有时候是满足不了我们的需求的。

如果想获取组件的数据,那么还是得靠作用域插槽!

先来一个例子说明下

<!--组件调用页面-->
<template>
  <div class="parent">
    <child>
      <template slot-scope="childData">
        <div v-for="(item, index) in childData.data" :key="index">
          <input type="text" :value="item"/>
        </div>
      </template>
    </child>
  </div>
</template>

<script>
import Child from '@/components/Child'
export default {
  components: {
    Child
  }
}
</script>
<!--封装的组件-->
<template>
  <div class="child">
    <h3>child组件标题</h3>
    <slot :data="list"></slot>
  </div>
</template>

<script>
export default {
  data () {
    return {
      list: ['1''2''3''4''5''6''7''8''9']
    }
  }
}
</script>
<style scoped>
</style>

看完这段代码,或许有些小伙伴就晕了。

别急,让我稍加说明!

其实简单的理解就是:封装的组件给要插入的组件传值

也可以说是变相的父给子组件传值

而template标签中自定义了 childData

childData 下的 data 也就是 组件内部的 list 数据

因为里面有段代码是 :data = "list"

这样一想简单易懂~

还有细心的小伙伴获取会注意到,我用了一个v-model!

试试例子,就会发现修改Input内容里面数据也会改变

那因为是input标签,vue中有封装自己的v-model

他可以双向绑定,那如果不是input标签,就可能需要自定义组件啦~

当然会有个办法提供给大家

<!--组件调用页面-->
<template>
  <div class="parent">
    <child>
      <template slot-scope="childData">
        <div v-for="(item, index) in childData.data.list" :key="index">
          <input
            type="text"
            :value="item"
            @input="changeValue(childData.data.change, index, $event)"
          />
        </div>
      </template>
    </child>
  </div>
</template>

<script>
import Child from '@/components/Child'
export default {
  components: {
    Child
  },
  methods: {
    /**
     * 改变 input 的 value 方法
     * @param change 子组件传入的函数方法(回调函数)
     * @param index  索引值
     * @param event  获取事件元素
     */
    changeValue (change, index, event) {
      console.log(111)
      const value = event.currentTarget.value
      change(index, value)
    }
  }
}
</script>
<template>
  <div class="child">
    <h3>child组件标题</h3>
    <slot :data="{list, change: onChange}"></slot>
  </div>
</template>

<script>
export default {
  data () {
    return {
      list: ['1''2''3''4''5''6''7''8''9']
    }
  },
  methods: {
    /**
     * 改变子组件 list 函数方法
     * @param index 数组索引值
     * @param value 新的 value 值
     */
    onChange (index, value) {
      this.list[index] = value
    }
  }
}
</script>
<style scoped>
</style>

简单的来说,就是在组件内部提供一个函数方法去让外部调用修改

如果你对这段话不太了解,那不妨看看另一篇简短而又干货满满的如何“修改”Vue中的prop~

感谢阅读