vue这个slot和angularJs的ng-transclude有点相似,vue slot的方式有很多种,下面会一一讲解到!
一. 插槽
概念:
我们经常需要向一个组件传递内容,vue就定义了一个slot元素,只要在需要的地方加入slot元素即可
作用:
让用户可以拓展组件,去更好地复用组件和对其做定制化处理(例如 一个上传图片的组件,它就是一个input按钮,可能在场景一,它的文本内容是上传图片,场景二是upload images,难道我每次都要去改组件的文本内容吗?,所以一般都是在外面把内容传进去达到复用的效果)
二. 插槽分类
1.默认插槽
我希望这个 <input>按钮 绝大多数情况下都渲染文本“图片上传”, 我们可以将“图片上传”作为后备内容,我们可以将它放在 <slot> 标签内
<div id="app">
<upload-images></upload-images>
</div>
<script>
Vue.component('upload-images', {
template: '<div><input type="file"><slot>{{childText}}</slot></input></div>',
data() {
return {
childText: "upload images"
}
}
});
new Vue({
el: "#app",
});
但是有时候却希望渲染文本为"upload images",则这个提供的内容将会被渲染从而取代后备内容
<div id="app">
<upload-images>{{parentText}}</upload-images>
</div>
<script>
Vue.component('upload-images', {
template: '<div><input type="file"><slot>{{childText}}</slot></input></div>',
data() {
return {
childText: "upload images"
}
}
});
new Vue({
el: "#app",
data: {
parentText: "图片上传"
}
});
插槽内可以包含任何模板代码,包括 HTML,甚至其它的组件!父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的,所以,父级模板里面不能访问childText,子级模板里面不能访问parentText
2.具名插槽
有些时候,我要指定插槽的位置,比如header模板放到一个指定插槽里面,footer模板里面放到一个指定插槽里面,其他的模板放到默认的插槽里面
<child id="app">
<template slot="header"> //或者不用<template>直接写<h2 slot="header">博文的标题</h2>
<h2>博文的标题</h2>
</template>
<template slot="section">
<p>博文的内容</p>//或者或者不用<template>直接写<h2 slot="section">博文的标题</h2>
</template>
<template slot="footer">
<p>博文的尾部</p>//或者或者不用<template>直接写<h2 slot="footer">博文的标题</h2>
</template>
<p>我是默认的插槽</p>
</child>
<script>
Vue.component('child', {
template: `<div>
<header>
<slot name="header"></slot>
<p>作者:张三</p>
</header>
<section>
<slot name="section"></slot>
</section>
<footer>
<slot name="footer"></slot>
</footer>
<slot></slot>
</div>`
});
new Vue({
el: "#app"
})
在 上使用特殊的 slot attribute,可以将内容从父级传给具名插槽 ,这里其实还有一个不带 name 的 slot插槽,也就是默认插槽,没有指定名字的模板内容会传递给子组件中不带 name 的 slot,最终渲染html为
<div id="app">
<header>
<h2>博文的标题</h2>
<p>作者:张三</p>
</header>
<section>
<p>博文的内容</p>
</section>
<footer>
<p>博文的尾部</p>
</footer>
<p>我是默认的插槽</p>
</div>
上面的写法是vue2.6版本之前的写法,自vue 2.6.0起被废弃,需要把 'slot="header" '改成'v-slot:header',或者改成‘缩写#header’,注意 v-slot 只能添加在 <template> 上 (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。
看下面新语法
<child id="app">
<template v-slot:header>
<h2>博文的标题</h2>
</template>
<template v-slot:section>
<p>博文的内容</p>
</template>
<template v-slot:footer>
<p>博文的尾部</p>
</template>
<p>我是默认的插槽</p>
</child>
<!-- 或者改成缩写 -->
<child id="app">
<template #header>
<h2>博文的标题</h2>
</template>
<template #section>
<p>博文的内容</p>
</template>
<template #footer>
<p>博文的尾部</p>
</template>
<p>我是默认的插槽</p>
</child>
3.作用域插槽
这里主要解决的是父组件在向子组件插槽传递模板内容时存在访问子组件数据的问题,我们知道插槽slot是处在子组件作用域里的,而插槽模板是处在父组件作用域里的,如果想在插槽模板里使用子组件的数据就需要作用域插槽
其实如果只是简单的父组件获取子组件数据,我们并不需要像上面这样,直接使用$emit和$on也可。作用域插槽的使用场景一般都是由于在子组件里对slot标签进行了v-for/v-if循环判断等,而slot模板又需要用到v-for/v-if上的判断循环参数,比如你想制作一个列表组件,有多个地方复用,但是只是html结构不一样,功能是一样的,难道你每次都要去改列表组件的模板吗? 你应该这么做,列表组件的模板是公用的,那些需要改动的html通过父组件传进去,那么此时父组件就要拿到子组件的数据了,就需要用到作用域插槽,目的是提高组件的复用性
<div id="app">
<h1>子组件</h1>
<child-component>
<template v-slot:listsname="itemOne">
<p>{{itemOne.obj.value}}</p>
</template>
</child-component>
<h1>子组件复用,html结构和之前的不一样,加个了个h3</h1>
<child-component>
<template v-slot:listsname="itemTwo">
<h3>{{itemTwo.obj.id}}.</h3>
<p>{{itemTwo.obj.value}}</p>
</template>
</child-component>
</div>
<script type="text/javascript">
Vue.component('child-component', {
template: `<div>
<slot name="listsname" v-for="list in lists" :obj="list"></slot>
</div>`,
data: function () {
return {
lists: [{
value: "床前明月光",
id:1
}, {
value: "疑是地上霜",
id:2
}, {
value: "举头望明月",
id:3
}, {
value: "低头思故乡",
id:4
}]
}
},
})
new Vue({
el: '#app'
})
</script>
子组件里面 通过 name="listsname" :obj="list" 把名称和值传到父组件,然后父组件通过 v-slot:listsname="itemOne" ,"itemOne"就是传过来的值相当于
let itemOne = {
obj: {
value: "床前明月光",
id: 1
}
};
只要出现多个插槽,需要用name 一一对应,如果只有默认插槽时,在父级作用域中,我们可以直接使用带值的 v-slot 来定义我们提供的插槽,或者用default来定义我们提供的插槽名字
上面只有一个插槽,也可以这样写
<div id="app">
<h1>子组件</h1>
<child-component>
<template v-slot="itemOne"> //或者 <template v-slot:default="itemOne">
<p>{{itemOne.obj.value}}</p>
</template>
</child-component>
<h1>子组件复用,html结构和之前的不一样,加个了个h3</h1>
<child-component>
<template v-slot="itemTwo">
<h3>{{itemTwo.obj.id}}.</h3>
<p>{{itemTwo.obj.value}}</p>
</template>
</child-component>
</div>
<script type="text/javascript">
Vue.component('child-component', {
template: `<div>
<slot v-for="list in lists" :obj="list"></slot>
</div>`,
data: function () {
return {
lists: [{
value: "床前明月光",
id: 1
}, {
value: "疑是地上霜",
id: 2
}, {
value: "举头望明月",
id: 3
}, {
value: "低头思故乡",
id: 4
}]
}
},
});
new Vue({
el: '#app'
})
</script>
vue 2.6之前的语法是 slot="listsname" slot-scope="itemOne",新语法是v-slot="itemOne",相等于合并了属性,旧的语法甚至 attribute 也可以直接用于非 <template> 元素 (包括组件)
//vue 2.6之前的语法
<child-component>
<p slot="default" slot-scope="itemOne">{{itemOne.obj.value}}</p>
</child-component>
//也可以这么写
<child-component>
<template slot="default" slot-scope="itemOne">
<p>{{itemOne.obj.value}}</p>
</template>
</child-component>
//vue 2.6之后的语法
<child-component>
<template v-slot:default="itemOne">
<p>{{itemOne.obj.value}}</p>
</template>
</child-component>
4.解构插槽
作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里
function (itemOne) {
// 插槽内容
}
这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop
<div id="app">
<h1>子组件</h1>
<child-component>
<template v-slot:listsname="{obj}">
<p>{{obj.value}}</p>
</template>
</child-component>
<br>
</div>
<script type="text/javascript">
Vue.component('child-component', {
template: `<div>
<slot name="listsname" v-for="list in lists" :obj="list"></slot>
</div>`,
data: function () {
return {
lists: [{
value: "床前明月光",
id:1
}, {
value: "疑是地上霜",
id:2
}, {
value: "举头望明月",
id:3
}, {
value: "低头思故乡",
id:4
}]
}
},
})
new Vue({
el: '#app'
})
</script>
在该插槽提供了多个 prop 的时候,还可以为prop 重命名
<child-component>
<template v-slot:listsname="{obj:list}">
<p>{{list.value}}</p>
</template>
</child-component>
甚至可以定义默认值,用于插槽 prop 是 undefined 的情形
<div id="app">
<h1>子组件</h1>
<child-component>
<template v-slot:listsname="{ obj = { value: '床前明月光' }}">
<p>{{obj.value}}</p>
</template>
</child-component>
</div>
<script type="text/javascript">
Vue.component('child-component', {
template: `<div>
<slot name="listsname"></slot>
</div>`
});
new Vue({
el: '#app'
})
</script>