什么是插槽?
概念:插槽,也就是slot,是组件的一块HTML模板,这块模板显示不显示、以及怎样显示由父组件来决定。
核心是:显示不显示和怎样显示。
插槽是一块模板,从模板种类的角度来分,其实都可以分为非插槽模板和插槽模板两大类。
从模板种类的角度来分,其实都可以分为非插槽模板和插槽模板两大类。它的显示与隐藏以及怎样显示由插件自身控制。
插槽模板是slot,它是一个空壳子,因为它显示与隐藏以及最后用什么样的html模板显示由父组件控制。但是插槽显示的位置确由子组件自身决定,slot写在组件template的哪块,父组件传过来的模板将来就显示在哪块。
-
单个插槽 | 默认插槽 | 匿名插槽
单个插槽是vue的官方叫法,但是其实也可以叫它默认插槽,或者与具名插槽相对,我们可以叫它匿名插槽。因为它不用设置name属性。
可以放置在组件的任意位置,但只能有一个。
例子:
父组件
<template>
<div class="father">
<h3>这里是父组件</h3>
<child>
<div class="tmpl">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
</child>
</div>
</template>
子组件
<template>
<div class="child">
<h3>这里是子组件</h3>
<slot></slot>
</div>
</template>
渲染结果为
<template>
<div class="father">
<h3>这里是父组件</h3>
<div class="child">
<h3>这里是子组件</h3>
<div class="tmpl">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
</div>
</div>
</template>
它将父组件child标签包括的内容全部替换为了子组件的内容,在有name的情况下父组件内的内容会自动根据子组件中的slot的name互相对应,但这里没有设置name,所以它就自动将没有对应的全部显示在匿名插槽中。也就是<slot></slot>标签中。
-
具名插槽
具名插槽加了name属性,它可以在一个组件中出现N次。出现在不同的位置。
下面的例子,就是一个有两个具名插槽和单个插槽的组件,这三个插槽被父组件用同一套css样式显示了出来,不同的是内容上略有区别。
父组件
<template>
<div class="father">
<h3>这里是父组件</h3>
<child>
<div class="tmpl" slot="up">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
<div class="tmpl" slot="down">
<span>菜单-1</span>
<span>菜单-2</span>
<span>菜单-3</span>
<span>菜单-4</span>
<span>菜单-5</span>
<span>菜单-6</span>
</div>
<div class="tmpl">
<span>菜单->1</span>
<span>菜单->2</span>
<span>菜单->3</span>
<span>菜单->4</span>
<span>菜单->5</span>
<span>菜单->6</span>
</div>
</child>
</div>
</template>
子组件
<template>
<div class="child">
// 具名插槽
<slot name="up"></slot>
<h3>这里是子组件</h3>
// 具名插槽
<slot name="down"></slot>
// 匿名插槽
<slot></slot>
</div>
</template>
渲染后为
<template>
<div class="father">
<h3>这里是父组件</h3>
<div class="child">
// 具名插槽
<slot name="up">
<div class="tmpl" slot="up">
<span>菜单1</span>
<span>菜单2</span>
<span>菜单3</span>
<span>菜单4</span>
<span>菜单5</span>
<span>菜单6</span>
</div>
</slot>
<h3>这里是子组件</h3>
// 具名插槽
<slot name="down">
<div class="tmpl" slot="down">
<span>菜单-1</span>
<span>菜单-2</span>
<span>菜单-3</span>
<span>菜单-4</span>
<span>菜单-5</span>
<span>菜单-6</span>
</div>
</slot>
// 匿名插槽
<slot>
<div class="tmpl">
<span>菜单->1</span>
<span>菜单->2</span>
<span>菜单->3</span>
<span>菜单->4</span>
<span>菜单->5</span>
<span>菜单->6</span>
</div>
</slot>
</div>
</div>
</template>
它根据slot插槽不同的name来不同渲染,最后把没有name的分配到匿名插槽中。
-
作用域插槽 | 带数据的插槽
作用域插槽要求,在slot上面绑定数据。
例如:
<slot name="up" :data="data"></slot>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
},
}
前面说了,插槽最后显示不显示是看父组件有没有在child下面写模板,像下面那样。
<child>
html模板
</child>
有html模板的情况,就是父组件会往子组件插模板的情况,具体插槽的样式呢,由父组件的html+css共同决定,但是这套的内容(数据)是作用域插槽决定。正因为作用域插槽绑定了一套数据,父组件可以拿来用。
于是,情况就变成了这样:样式父组件说了算,但内容可以显示子组件插槽绑定的。
作用域插槽和单个插槽和具名插槽的区别
因为单个插槽和具名插槽不绑定数据,所以父组件是提供的模板要既包括样式由包括内容的,就好像在上面的例子中,你看到的文字内容,“菜单1”,“菜单2”什么的都是父组件自己提供的内容;而作用域插槽,父组件只需要提供一套样式(在确实用作用域插槽绑定的数据的前提下)。
例子:
父组件
<template>
<div class="father">
<h3>这里是父组件</h3>
<!--第一次使用:用flex展示数据-->
<child>
<template slot-scope="user">
<div class="tmpl">
<span v-for="item in user.data">{{item}}</span>
</div>
</template>
</child>
<!--第二次使用:用列表展示数据-->
<child>
<template slot-scope="user">
<ul>
<li v-for="item in user.data">{{item}}</li>
</ul>
</template>
</child>
<!--第三次使用:直接显示数据-->
<child>
<template slot-scope="user">
{{user.data}}
</template>
</child>
<!--第四次使用:不使用其提供的数据, 作用域插槽退变成匿名插槽-->
<child>
我就是模板
</child>
</div>
</template>
子组件
<template>
<div class="child">
<h3>这里是子组件</h3>
// 作用域插槽
<slot :data="data"></slot>
</div>
</template>
export default {
data: function(){
return {
data: ['zhangsan','lisi','wanwu','zhaoliu','tianqi','xiaoba']
}
}
}
效果如图:
其中代码:
slot-scope="user"中 的"user’是可以自定义的,只是承载data的载体而已。而子组件中的:data="data"中的data是下面的模拟数据,:data不需要声明。
大意:
父子组件编译模版是有作用域的,在父组件是无法直接使用子组件数据的,所以在子组件插槽中使用v-bind(例子中用的是:语法糖)给插槽绑定了子组件的data数据,这样在父组件中可以使用slot-scope=xxx (后面这个xxx和函数形参一样,是个变量,随便什么名字都可以)来使用子组件中的数据了 这样就做到了父组件既决定html模版+样式 ,还使用了子组件中的状态。
关于slot-scope解释:从子组件传过来的名为data值在父组件将会被slot-scope接收并命名成为为user的数据。子组件的数据通过slot-scope属性传递到了父组件。
补充:
关于父子组件通讯的话,我们常用的是父子组件数据时刻同步的
所以给个例子
这是父组件中的template:
<son :foo="bar" v-on:update="val => bar = val"></son>
在子组件中, 我们通过props声明的方式接收foo并使用
props: {
foo: [type]
}
同时每当子组件中数据改变的时候,通过
this.$emit('update', newValue)
把参数newValue传递给父组件template中监听函数中的"val"。然后通过
val => bar = val
这个表达式就实现了bar = newValue. 这个时候,我们发现父组件中的关键数据bar被子组件改变(相等)了! 通过数据的双向绑定, 父(组件)可以修改子的数据, 子也可以修改父的数据 Vue提供了sync修饰符简化上面的代码,例如:
<comp :foo.sync="bar"></comp>
会被扩展为:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
然后你需要在子组件中改变父组件数据的时候, 需要触发以下的自定义事件:
this.$emit("update:foo", newValue)
注意:
父可以改变子(数据), 但子不能直接改变父(数据)!, 父中数据的变动只能由它自己决定