插槽

67 阅读5分钟

普通插槽

基于一个需求

 const app = Vue.createApp({
    data() {
        return {
            count: 'a',
        }
    },
    template: `
    <div>
        <myForm />
        <myForm />
    </div>
    `
})
app.component('myForm', {
    methods:{
        handleClick() {
            alert(123)
        }
    },
    template: `
      <div>
        <input/>
        <button @click="handleClick">提交</button>
      </div>
    `
})
const vm = app.mount('#root')

image.png

如果调用第一个myForm组件的时候,使用div来提交,调用第二个myForm组件的时候,使用button按钮来提交,这个需求该如何实现呢?

使用插槽slot吧

const app = Vue.createApp({
    data() {
        return {
            count: 'a',
        }
    },
    template: `
    <div>
        <myForm>
            <div>提交</div>
        </myForm >
        <myForm>
            <button>提交</button>
        </myForm>
    </div>
    `
})
app.component('myForm', {
    methods: {
        handleClick() {
            alert(123)
        }
    },
    template: `
      <div>
        <input/>
        <slot></slot>
      </div>
    `
})
const vm = app.mount('#root')

image.png

  1. 定义插槽:在2个组件标签之间插入的内容(一般为DOM元素)被称为插槽

  2. 使用插槽:组件中使用<slot></slot>来接收插槽中的内容

注意

<slot></slot>标签上是没法直接绑定事件的

但是可以通过在slot标签外包裹类似span标签,将事件绑定在span标签上的方式添加事件

const app = Vue.createApp({
    data() {
        return {
            count: 'a',
        }
    },
    template: `
    <div>
        <myForm>
            <div>提交</div>
        </myForm >
        <myForm>
            <button>提交</button>
        </myForm>
    </div>
    `
})
app.component('myForm', {
    methods: {
        handleClick() {
            alert(123)
        }
    },
    template: `
      <div>
        <input/>
        <span @click="handleClick">
            <slot ></slot>
        </span>
      </div>
    `
})
const vm = app.mount('#root')

为什么要使用slot

如果没有slot,要想做组件之间的传值(传DOM),我们必须通过父组件给子组件传添加自定义属性,子组件通过props的方式接收

 <myForm dom="<div>提交</div>">
   <div>提交</div>
 </myForm >

但是涉及到DOM的传递,如果还用之前的props的方式传递会非常麻烦,有了插槽之后DOM的传递就会变得简单很多

父组件传递DOM元素给子组件,只需要在调用组件的时候,在组件标签之间添加要传递的DOM便签即可

子组件如果想要接收父组件传递过来的DOM,只需要使用<slot></slot>标签接收父组件传递过来的DOM元素

插槽中可以传哪些值?

插槽中除了可以传DOM标签之外,还可以传数字、字符串等基础数据类型的数据,还可以传子组件

const app = Vue.createApp({
    data() {
        return {
            count: 'a',
        }
    },
    template: `
    <div>
        <myForm>
            <test/>
        </myForm >
        <myForm>
           123
        </myForm>
    </div>
    `
})
app.component('test',{
    template: `<div>test</div>`
})
app.component('myForm', {
    methods: {
        handleClick() {
            alert(123)
        }
    },
    template: `
      <div>
        <input/>
        <span @click="handleClick">
            <slot ></slot>
        </span>
      </div>
    `
})
const vm = app.mount('#root')

slot中使用的数据作用域的问题

eg: 在插槽中使用data中的变量

 const app = Vue.createApp({
    data() {
        return {
            text: '提交',
        }
    },
    template: `
    <div>
        <myForm>
            <div>{{text}}</div>
        </myForm >
        <myForm>
            <button>{{text}}</button>
        </myForm>
    </div>
    `
})
app.component('test',{
    template: `<div>test</div>`
})
app.component('myForm', {
    methods: {
        handleClick() {
            alert(123)
        }
    },
    template: `
      <div>
        <input/>
        <span @click="handleClick">
            <slot ></slot>
        </span>
      </div>
    `
})
const vm = app.mount('#root')

备注

虽然父组件在插槽中传递的DOM标签会替换掉子组件中的<slot></slot>

 const app = Vue.createApp({
            data() {
                return {
                    text: '提交',
                }
            },
            template: `
            <div>
                <myForm>
                    <div>{{text}}</div>
                </myForm >
                <myForm>
                    <button>{{text}}</button>
                </myForm>
            </div>
            `
        })
        app.component('test',{
            template: `<div>test</div>`
        })
        app.component('myForm', {
            methods: {
                handleClick() {
                    alert(123)
                }
            },
            template: `
              <div>
                <input/>
                <span @click="handleClick">
                   // <slot ></slot>
                   // 相当于
                   <div>{{text}}</div>
                </span>
              </div>
            `
        })
        const vm = app.mount('#root')
        

但是父组件传递给子组件插槽中的变量,是父组件作用域中的变量而不是子组件作用域中的变量

备注

父模板中调用的数据,使用的是父模板中的数据

子模板中调用的数据,使用的是子模板中的数据

slot默认值设置

如果父组件调用子组件的时候,没有给子组件任何插槽的内容,有时就需要给插槽一个默认值,方法是在<slot>default value</slot>标签中间加上默认值

const app = Vue.createApp({
    data() {
        return {
            text: '提交',
        }
    },
    template: `
    <div>
        <myForm>
            <div>{{text}}</div>
        </myForm >
        <myForm>
            <button>{{text}}</button>
        </myForm>
        <myForm>
        </myForm >
    </div>
    `
})
app.component('test',{
    template: `<div>test</div>`
})
app.component('myForm', {
    methods: {
        handleClick() {
            alert(123)
        }
    },
    template: `
      <div>
        <input/>
        <span @click="handleClick">
            <slot>default value</slot>
        </span>
      </div>
    `
})
const vm = app.mount('#root')

它的意思是如果,父组件给子组件的插槽传了内容,那么就展示这个插槽中的内容,如果没传那么就使用插槽的默认值

具名插槽

使用方法

假如现在有这样一个需求:

子组件中有已经定义好的content区域,同时还有header区域和footer区域,这2部分区域不是子组件自己定义的,而是需要父组件传递过来,传什么子组件就展示什么

 const app = Vue.createApp({
    template: `
    <div>
        <layout>
            <div>header</div>
            <div>footer</div>
        </layout >
    </div>
    `
})
app.component('layout', {
    template: `
      <div>
        <slot></slot>
        <div>content</div>
      </div>
    `
})
const vm = app.mount('#root')

image.png

存在的问题,header和footer只能整体作为插槽中的内容放入<slot></slot>中,没法指定heaer和footer放在指定位置,如何解决这个问题呢?

使用具名插槽吧

 const app = Vue.createApp({
    template: `
    <div>
        <layout>
            <template v-slot:header>
                <div>header</div>
            </template >
            <template v-slot:footer>
                <div>footer</div>
            </template >
        </layout >
    </div>
    `
})
app.component('layout', {
    template: `
      <div>
        <slot name="header"></slot>
        <div>content</div>
        <slot name="footer"></slot>
      </div>
    `
})
const vm = app.mount('#root')

image.png

备注

  1. 在插槽的内容中使用v-slot:xxx将一个大的插槽拆分成若干小的插槽,并给它们起名字,注意v-slot:xxx不能直接放在插槽内容的标签上而是应该放到template标签上
  2. 在使用插槽的地方加上name指定使用的是哪个具体的插槽<slot name="xxx"></slot>

一个大的slot被拆分成多个部分slot,并给每个部分slot起个名字,那么这些部分slot叫具名插槽

具名插槽的简写

使用#号对插槽的名字进行简写

const app = Vue.createApp({
    template: `
    <div>
        <layout>
            <template #header>
                <div>header</div>
            </template >
            <template #footer>
                <div>footer</div>
            </template >
        </layout >
    </div>
    `
})
app.component('layout', {
    template: `
      <div>
        <slot name="header"></slot>
        <div>content</div>
        <slot name="footer"></slot>
      </div>
    `
})
const vm = app.mount('#root')

作用域插槽

使用

如果现在有这样一个需求:

子组件中循环展示数组中的数据,但是子组件中包裹数据的标签不想在子组件中写死,而是由父组件指定,该如何实现呢?

const app = Vue.createApp({
    template: `
    <div>
        <list></list >
    </div>
    `
})
app.component('list', {
    data() {
        return {
            list: [1,2,3]
        }
    },
    template: `
      <div>
        <div v-for="item in list">{{item}}</div>
      </div>
    `
})
const vm = app.mount('#root')

如上面所示,如果不想用div作为包裹item的容器呢? 我们会想到使用插槽

const app = Vue.createApp({
    template: `
    <div>
        <list>
            <span>{{item}}</span>
        </list >
    </div>
    `
})
app.component('list', {
    data() {
        return {
            list: [1,2,3]
        }
    },
    template: `
      <div>
        <slot v-for="item in list"/>
      </div>
    `
})
const vm = app.mount('#root')

我们发现没办法在父组件中去使用子组件中的数据item啊,目前的item只是父组件中item,而item父组件中是没有的,这样写是不行的!那么我们应该怎么把子组件的item传给父组件呢?

在调用slot的地方,需要通过属性将值传给slot,在定义slot的地方使用v-slot="xxx"的方式接收传过来的全部的值(对象)

const app = Vue.createApp({
    template: `
    <div>
        <list v-slot="slotProps">
            <span>{{slotProps.item}}</span>
        </list >
    </div>
    `
})
app.component('list', {
    data() {
        return {
            list: [1,2,3]
        }
    },
    template: `
      <div>
        <slot v-for="item in list" :item="item"/>
      </div>
    `
})
const vm = app.mount('#root')

运行结果

image.png

为什么要有作用域插槽?

当子组件渲染的内容要由父组件决定的时候,同时需要父组件去使用子组件中的数据