在开发中,我们抽取了一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制死了。我们应该让使用者决定这一块区域应该显示什么内容。那么应该怎么做呢?这就需要使用所谓的插槽了,那什么是插槽呢?
什么是插槽
简单来说,插槽(slot)相当于一个坑,你想往这个坑填什么内容就填什么内容。它允许组件的使用者在父组件的标签中插入自定义的内容,从而增强组件的灵活性和可重用性。React中插槽实现的核心就是要用双闭合标签<ComponentName>...</ComponentName>
调用组件。双闭合里传递的内容,都会变成虚拟DOM放在组件参数props
的children
属性中。
默认插槽
父组件在双闭合标签内添加内容
function App(){
return <Child title='个人信息'>
<div>姓名:小浩</div>
</Child>
}
子组件打印props
查看里面包含哪些属性
class Child extends React.Component{
render(){
// 输出查看
console.log(this.props)
return <div className="child">
<h2>{this.props.title}</h2>
</div>
}
}
浏览器输出
通过上面浏览器显示可以知道,在父组件双闭合标签<Child>
内如果写了内容,那么在props
属性里会增加一个children
属性对象,这个children
就是写在双闭合标签内的div
标签和标签内的内容,而它被编译为了虚拟DOM对象,在这个虚拟DOM对象内还包含了个props
。
而在children
里的type
就是父组件双闭合标签内写的div
标签,里面那个props
的children
属性的值就是div
标签内的内容,且我们在父组件双闭合标签内写的内容<div>姓名:小浩</div>
没有在页面显示出来。这是为啥呢?
这就需要所谓的插槽来做工作了,看下面代码:
class Child extends React.Component{
render(){
console.log(this.props)
const { children } = this.props
return <div className="child">
<h2>{this.props.title}</h2>
{/*插入children内容*/}
{children}
</div>
}
}
浏览器输出
以上添加了{children}
这个后,在父组件双闭合标签内写的内容在页面渲染出来了,这个就是插槽操作了。
那如果双闭合标签内有多个元素又是怎样的呢?
function App(){
return <Child title='个人信息'>
<div>姓名:小浩</div>
<div>性别:男</div>
<div>年龄:30</div>
<div>工作:程序员</div>
<div>介绍:暂无介绍</div>
</Child>
}
浏览器输出
可以看到props
里的children
变成了一个数组,里面每一项元素就是在父组件双闭合标签内写的内容。
通过以上可以知道,props
里的children
如果只有一个元素到的时候是一个对象,有多个元素的时候是一个数组。
那么问题就来了,我们就不能直接通过children[0]、children[1]...
这样的方式拿到元素到指定的位置渲染。
举例
这里修改下父组件结构布局
function App(){
return <Child title='个人信息'>
<div>
<div>姓名:小浩</div>
<div>性别:男</div>
<div>年龄:30</div>
<div>工作:程序员</div>
<div>介绍:暂无介绍</div>
</div>
<div>
<button>取消</button>
<button>修改</button>
</div>
</Child>
}
子组件通过children[0]、children[1]
这样的方式拿对应的元素在指定位置渲染
class Child extends React.Component{
render(){
console.log(this.props)
const { children } = this.props
return <div className="child">
<h2>{this.props.title}</h2>
{children[0]}
{children[1]}
</div>
}
}
以上渲染没有问题,那如果去掉下面的button
的div
呢?
父组件去掉button
function App(){
return <Child title='个人信息'>
<div>
<div>姓名:小浩</div>
<div>性别:男</div>
<div>年龄:30</div>
<div>工作:程序员</div>
<div>介绍:暂无介绍</div>
</div>
</Child>
}
子组件打印下children[0],children[1]
class Child extends React.Component{
render(){
// 打印下React看看有些啥东西
console.log('React-------',React)
console.log(this.props)
let { children } = this.props
// 打印下children
console.log('children-------',children[0],children[1])//undefined undefined
return <div className="child">
<h2>{this.props.title}</h2>
{children[0]}
{children[1]}
</div>
}
}
浏览器输出
浏览器运行控制台输出props
里的children
是个对象,通过children[0],children[1]
获取就会输出undefined undefined
,所以在使用children[0],children[1]
这种方式渲染时,需要先处理children
的类型。
上面在控制台打印了下React
对象看看有些啥东西,这里就基于React.Children
对象中提供的方法map/forEach/count/toArray/only
,对props.children
的类型做处理。因为在这些方法的内部,已经对children
的各种形式做了处理。
class Child extends React.Component{
render(){
let { children } = this.props
//如果children不是数组,就转为数组
children = React.Children.toArray(children)
return <div className="child">
<h2>{this.props.title}</h2>
{children[0]}
{children[1]}
</div>
}
}
以上利用React.Children.toArray()
方法处理children
后,就可以使用children[0],children[1]
这样的方式获取元素了,如果没有下标对应的元素就是undefined
。
通过上面children
出现过的类型得知,this.props.children
的值有三种可能:
- 如果当前组件没有子节点,它就是
undefined
; - 如果有一个子节点,数据类型是
Object
; - 如果有多个子节点,数据类型就是
Array
。
所以,处理 this.props.children
的时候要小心。
通过children[0],children[1]
数组下标来拿元素插入渲染话,那么父组件双闭合标签内写的元素顺序就不能乱。
function App(){
return <Child title='个人信息'>
<div>
<button>取消</button>
<button>修改</button>
</div>
<div>
<div>姓名:小浩</div>
<div>性别:男</div>
<div>年龄:30</div>
<div>工作:程序员</div>
<div>介绍:暂无介绍</div>
</div>
</Child>
}
上面代码修改了按钮和用户信息的顺序后页面效果:
可以看到按钮跑到用户信息的上面了,这样显然是不行的,那有什么办法处理呢?
具名插槽
具名插槽: 具名插槽字面意思就是给插槽取个名字。
目的: 在调用组件,传递插槽信息的时候,我们可以不用考虑顺序直接设置好对应的名字即可! ! !
那要怎么取呢?看下面这段代码:
function App(){
return <Child title='个人信息'>
<div slotname="button">
<button>取消</button>
<button>修改</button>
</div>
<div slotname="info">
<div>姓名:小浩</div>
<div>性别:男</div>
<div>年龄:30</div>
<div>工作:程序员</div>
<div>介绍:暂无介绍</div>
</div>
<div>我是默认显示的内容</div>
</Child>
}
上面给父组件双节点标签内的每个节点添加个属性slotname
,这个就是插槽的名字,这个属性是自定义的。
class Child extends React.Component{
render(){
let { children } = this.props
//如果children不是数组,就转为数组
children = React.Children.toArray(children)
// 打印查看children属性
console.log(children)
// children元素分类
let userInfo = [],buttonList = [], defaults = []
// 遍历
children.forEach(child =>{
let { slotname } = child.props
switch(slotname){
case 'button':
buttonList.push(child)
break;
case 'info':
userInfo.push(child)
break;
default:
defaults.push(child)
}
})
return <div className="child">
<h2>{this.props.title}</h2>
{/*用户信息*/}
{userInfo}
{/*默认显示内容 未取名字的*/}
{defaults}
{/*按钮列表*/}
{buttonList}
</div>
}
}
浏览器输出
传递进来的插槽信息,都是编译为虚拟DOM后传递进来的,可以看到props
里面有个slotname
属性,就是给节点插槽取的名字。上面代码通过slotname
属性来判断归类,然后在指定位置插入归类好了的元素列表。这样不管你父组件里的顺序怎么变,子组件里都不会再受到任何影响。
总结:React的插槽其实就是要在子组件对的位置渲染this.props.children
内的虚拟DOM,也是对this.props.children
内的虚拟DOM合理的操作使用。