普通插槽
基于一个需求
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')
如果调用第一个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')
定义插槽:在2个组件标签之间插入的内容(一般为DOM元素)被称为插槽
使用插槽:组件中使用
<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')
存在的问题,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')
备注
- 在插槽的内容中使用
v-slot:xxx将一个大的插槽拆分成若干小的插槽,并给它们起名字,注意v-slot:xxx不能直接放在插槽内容的标签上而是应该放到template标签上 - 在使用插槽的地方加上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')
运行结果
为什么要有作用域插槽?
当子组件渲染的内容要由父组件决定的时候,同时需要父组件去使用子组件中的数据