React中的插槽

在开发中,我们抽取了一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制死了。我们应该让使用者决定这一块区域应该显示什么内容。那么应该怎么做呢?这就需要使用所谓的插槽了,那什么是插槽呢?

什么是插槽

简单来说,插槽(slot)相当于一个坑,你想往这个坑填什么内容就填什么内容。它允许组件的使用者在父组件的标签中插入自定义的内容,从而增强组件的灵活性和可重用性。React中插槽实现的核心就是要用双闭合标签<ComponentName>...</ComponentName>调用组件。双闭合里传递的内容,都会变成虚拟DOM放在组件参数propschildren属性中。

默认插槽

父组件在双闭合标签内添加内容

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>
 }
}

浏览器输出
react插槽children
通过上面浏览器显示可以知道,在父组件双闭合标签<Child>内如果写了内容,那么在props属性里会增加一个children属性对象,这个children就是写在双闭合标签内的div标签和标签内的内容,而它被编译为了虚拟DOM对象,在这个虚拟DOM对象内还包含了个props
而在children里的type就是父组件双闭合标签内写的div标签,里面那个propschildren属性的值就是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>
 }
}

浏览器输出
react插槽children
以上添加了{children}这个后,在父组件双闭合标签内写的内容在页面渲染出来了,这个就是插槽操作了。
那如果双闭合标签内有多个元素又是怎样的呢?

function App(){
  return <Child title='个人信息'>
      <div>姓名:小浩</div>
      <div>性别:男</div>
      <div>年龄:30</div>
      <div>工作:程序员</div>
      <div>介绍:暂无介绍</div>
  </Child>
}

浏览器输出
react插槽children
可以看到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>
 }
}

以上渲染没有问题,那如果去掉下面的buttondiv呢?
父组件去掉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>
 }
}

浏览器输出
react插槽children
浏览器运行控制台输出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>
}

上面代码修改了按钮和用户信息的顺序后页面效果:
react插槽children
可以看到按钮跑到用户信息的上面了,这样显然是不行的,那有什么办法处理呢?

具名插槽

具名插槽: 具名插槽字面意思就是给插槽取个名字。
目的: 在调用组件,传递插槽信息的时候,我们可以不用考虑顺序直接设置好对应的名字即可! ! !
那要怎么取呢?看下面这段代码:

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>
 }
}

浏览器输出
react插槽children
传递进来的插槽信息,都是编译为虚拟DOM后传递进来的,可以看到props里面有个slotname属性,就是给节点插槽取的名字。上面代码通过slotname属性来判断归类,然后在指定位置插入归类好了的元素列表。这样不管你父组件里的顺序怎么变,子组件里都不会再受到任何影响。

总结:React的插槽其实就是要在子组件对的位置渲染this.props.children内的虚拟DOM,也是对this.props.children内的虚拟DOM合理的操作使用。


  转载请注明: 小浩之随笔 React中的插槽

 上一篇
初识React函数组件和类组件 初识React函数组件和类组件
在React组件化开发中,把组件分为函数组件、类组件、Hooks组件,这里本文先认识函数组件和类组件。 函数组件 使用 JS 函数(普通,箭头)创建的组件,就是函数组件,函数组件也称“静态组件”。 定义函数组件 函数名称首字母必需大写,R
2022-04-07
下一篇 
React中组件的props属性 React中组件的props属性
什么是Props props是组件(函数组件和class组件)间的内置属性,用于在组件之间传递数据。它是父组件向子组件传递信息的一种方式,通过 props,父组件可以向子组件传递数据、函数、配置项等。 初次使用Props 先定义一个父组件
2022-04-06
  目录