11. React组件插槽用法

4. React组件插槽用法

4.1. React中的插槽(Slot)
    1. 在开发中,我们抽取一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素
    1. 这种需求在Vue当中有一个固定的做法是通过slot来完成的,React呢?
    • 在React中是没有插槽的概念的,或者说React中不需要插槽,因为React太灵活了,React中可以通过多种形式来实现插槽的效果,例如可以传递任意属性给子组件
    1. React对于这种需要插槽的情况非常灵活,有两种方案可以实现:
    • 组件的children子元素
    • props属性传递React元素`;
4.2. children实现插槽
    1. 每个组件之间都可以获取到props.children属性:它包含组件的开始标签和结束标签之间的内容
    1. 示例代码如下:
    • 如图:

    • 父组件代码:

    js 复制代码
    // React中是没有插槽的概念的,或者说React中不需要插槽,因为React太灵活了,React中可以通过多种形式来实现插槽的效果,例如可以传递任意属性给子组件
    import React, { Component } from 'react'
    import NavBar from './nav-bar'
    
    export class App extends Component {
      render() {
        // NavBar实例对象 -> this
        // this.props.children -> [button, h2, i]
        return (
          <div>
            <NavBar>
              <button>按钮</button>
                <h2>我是标题</h2>
                <i>我是斜体字</i>
            </NavBar>
          </div>
        )
      }
    }
    
    // function createElement(type, props, children) {
    //   children -> arguments.length - 2
    // }
    
    export default App
    • 子组件代码:
    js 复制代码
      import React, { Component } from 'react'
      import './style.css'
      import PropTypes from 'prop-types'
    
      export class NavBar extends Component {
        render() {
          const { children } = this.props
          // 子元素放多个的时候,children是一个数组,一个的时候是一个对象
          console.log('children===', children)
          return (
            <div className='nav-bar'>
              <div>{children}</div> 
              {/* <div className="left">{children[0]}</div>
              <div className="center">{children[1]}</div>
              <div className="right">{children[2]}</div> */}
            </div>
          )
        }
      }
    
      NavBar.propTypes = {
        children: PropTypes.array
      }
    
      export default NavBar
4.3. props实现插槽
    1. 通过children实现的方案虽然可行,但是有以下两个弊端:
    • children的弊端
      • children可能是一个元素也可能是数组,使时需要慎重
      • children对索引顺序要求太高,通过索引值获取传入的元素很容易出错不能准确的获取传入的元素
    1. 另外一种方案就是使用props实现
    • 通过具体的属性名,可以让我们在传入和获取时更加的精准
    1. 实例代码如下:
    • 父组件代码如下:
    js 复制代码
      // React中是没有插槽的概念的,或者说React中不需要插槽,因为React太灵活了,React中可以通过多种形式来实现插槽的效果,例如可以传递任意属性给子组件
      import React, { Component } from 'react'
      import NavBar from './nav-bar'
      import NavBarTwo from './nav-bar-two'
    
      export class App extends Component {
        render() {
          // NavBar实例对象 -> this
          // this.props.children -> [button, h2, i]
          const btn = <button>按钮</button>
          
          return (
            <div>
              {/* 1.吃用children实现插槽 */}
              <NavBar>
                <button>按钮</button>
                  <h2>我是标题</h2>
                  <i>我是斜体字</i>
              </NavBar>
              {/* 2.使用props现插槽实 */}
              {/* 推荐使用props方案,children有点不可控 */}
              <NavBarTwo
                style={{marginTop: '20px'}}
                leftSlot={ btn }
                centerSlot={ <h2>呵呵呵呵</h2>  }
                rightSlot={ <i>斜体2</i> }
              />
            </div>
          )
        }
      }
    
      // function createElement(type, props, children) {
      //   children -> arguments.length - 2
      // }
    
      export default App
    • 子组件代码如下:
    js 复制代码
      import React, { Component } from 'react'
    
      export class NavBarTwo extends Component {
        render() {
          const { leftSlot, centerSlot, rightSlot } = this.props
          return (
            <div className='nav-bar'>
              <div className="left">{leftSlot}</div>
              <div className="center">{centerSlot}</div>
              <div className="right">{rightSlot}</div>
            </div>
          )
        }
      }
    
      export default NavBarTwo
4.4. 作用域插槽实现
    1. 作用域插槽案例;现在子组件的item都是span,希望子组件的内容是根据父组件传进来的类型渲染
  • 父组件代码:

    js 复制代码
      import React, { Component } from 'react'
      import TabControl from './TabControl'
    
      export class App extends Component {
        constructor() {
          super()
    
          this.state = {
            titles: ['流行', '新款', '热门'],
            tabIndex: 0
          }
        }
        tabClick (index) {
          this.setState({
            tabIndex: index
          })
        }
        getTabItem (item) {
          if(item ==='流行') {
            return <span>{item}</span>
          } else if(item === '新款') {
            return <button>{item}</button> 
          } else {
            return <i>{item}</i>
          }
        }
        render() {
          const { titles,tabIndex } = this.state
          // 现在子组件的item都是span,希望子组件的内容是根据父组件传进来的类型渲染
          // 作用域插槽
          return (
            <div className='app'>
              <TabControl 
                titles={ titles } 
                tabClick={i => this.tabClick(i)}
                // itemType={(item) => <button>{item}</button>}/>
                itemType={(item) => this.getTabItem(item)}/>
                <h2>{titles[tabIndex]}</h2>
            </div>
          )
        }
      }
    
      export default App
  • 子组件代码:

    js 复制代码
    import React, { Component } from 'react'
    import './style.css'
    
    
    export class TabControl extends Component {
      constructor() {
        super()
        this.state = {
          currentIndex: 0
        }
      }
    
      changeTab (index) {
        this.props.tabClick(index)
        this.setState({
          currentIndex: index
        })
      } 
    
      render() {
        const { titles, itemType } =  this.props
        const { currentIndex } = this.state
        return (
          <div>
            <div className="tab-control">
                {
                  titles.map((item, index) => {
                    return (
                    <div 
                      className={`item ${currentIndex === index ? 'active' : ''}`} 
                      key={index}
                      onClick={(e) => this.changeTab(index)}>
                      {/* <span className='text'  >{item}</span> */}
                      {itemType(item)}
                    </div>
                    )
                  })
                }
            </div>
          </div>
        )
      }
    }
    
    export default TabControl