React Children 使用


  React 有一個特殊的屬性children, 主要用於組件需要渲染內容,但它並不知道具體要渲染什么內容,怎么會有這種使用場景?確實比較少,但並不是沒有,比如彈出框。當你寫一個彈出框組件的時候,你知道它要彈出什么嗎?肯定不知道,只有使用的時候才知道。那為什么要寫彈出框組件?雖然內容不一樣,但框是一致的,居中啊,陰影啊,寬度啊,高度啊,每一個彈出框都一樣,所以有必要形成一個組件,代碼復用。框寫好了,那到時候用的時候,具體的內容怎么放到框里面?那就要在框中占一個位置,如果有內容候就放到這個地方,占位置使用的就是children.

  根據描述,children 最簡單的使用場景就是一個組件中直接寫this.props.children,調用這個組件的時候,再具體寫內容。這里要注意的是組件調用方式,.假設包含children的組件叫theme, 那么調用的時候,就要在theme組件兩個標簽之間寫內容,<theme><message/></theme>, 只有這樣<message />才會被theme組件中的children 獲取到。為什么要這要寫?想一想html 標簽, 只有在兩個標簽之間內容才被稱之為children, 這里的children 也是同樣的道理,只不過標簽換成了組件。

  使用create-react-app 創建一個項目 react-children,實踐一下,要使用boostrap 提供樣式, 所以 cd react-children && npm install bootstrap --save, 使用vs code 編輯器打開項目, 在index.js中引入bootstrap css 樣式

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';// 添加bootstrap 樣式
import 'bootstrap/dist/css/bootstrap.css'; 

ReactDOM.render(<App />, document.getElementById('root'));

  在src 目錄下創建一個簡單的組件ThemeSelector, 它就是只包含this.props.children

import React from 'react';
export default function ThemeSelector(props) { return ( <div className="bg-dark p-2"> <div className="bg-info p-2"> {props.children} </div> </div> ) }

  然后創建兩個ThemeSelector要真正渲染的組件, ActionButton和Message

import React from "react";
export default function Message(props) {
    return (
        <div className={`h5 bg-${props.theme} text-white p-2`}>
            {props.message}
        </div>
    )
}
import React from "react";
export default function ActionButton(props) {
    return (
        <button className={` btn btn-${props.theme} m-2`}
            onClick={props.callback}>
            {props.text}
        </button>
    )
}

  在App.js中調用這三個組件,使ActionButton和Message 真正渲染到ThemeSelector 中

import React, { Component } from 'react';
import Message  from "./Message";
import ActionButton from './ActionButton';
import ThemeSelector from './ThemeSelector';

export default class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0
    }
  }
  incrementCounter = () => {
    this.setState({ counter: this.state.counter + 1 });
  }

  render() {
    return <div className="m-2 text-center">
      {/* 真正要渲染的內容入到<ThemeSelector>標簽 */}
      <ThemeSelector>
        <Message theme="primary"
          message={`Counter: ${this.state.counter}`} />
        <ActionButton theme="secondary"
          text="Increment" callback={this.incrementCounter} />
      </ThemeSelector>
    </div>
  }
}

  npm start 啟動服務器,效果如下,非常簡單,

  綜上所述,可以簡單總結一下,children 只是一個占位符,使用children 屬性的組件,它也只是提供了一個架子(容器),children真正的內容,只有在調用組件的時候才能確認。這么做的好處,就是架子(容器)的代碼可以復用,這也給我們一個提示, 只要有多個組件的容器一致,就可以使用childern 屬性。

   我以前對children 屬性的認知也僅限於此,直到最近,看了一本書,才知道React 還提供了一些操作children 的方法,進而知道了children 可以是任何內容, 這些方法正是為了children 屬性量身定做的。重新認識一下children, 它可以是字符串,可以是函數,,可以是數組,可以是undefined , 可以是null, 可以是Boolean,幾乎,你想到的都可以是children的內容。

<ThemeSelector>123</ThemeSelector>
<ThemeSelector>{undefined}</ThemeSelector>
<ThemeSelector>{null}</ThemeSelector>
<ThemeSelector>{(() => 'helloWorld')()}</ThemeSelector>
<ThemeSelector>{[4,5,6]}</ThemeSelector>
<ThemeSelector>{false} {true}</ThemeSelector>

  只不過 null ,undefined,false, true 並不會渲染到頁面上,由於提供給children 內容的各種各樣,this.props.children的返回值也是不同,如果children沒有獲取到內容,它返回的就是undefined, 如果只有一個內容, 它返回的就是一個object, 如果有多個,它返回的就是數組。正是由於children的復雜性,React才提從提供了以下幾個方法來幫我們正確地使用children 屬性。

  React.Children.map: 和數組的map 方法一致,接受children 和一個函數,返回map 后的children, 返回的是一個數組。函數的參數第一個是child組件,第二個是 index索引

  React.Children.forEach: 和React.Children.map 的使用方法一致,只不過不返回內容

  React.children.count: 接受children 作為參數,計算children的數量

  React.children.only: 判斷獲取的children是不是只有一個,如果是,就返回這個children, 如果不是,則報錯。

  React.children.toArray: 把children 轉化為數組,可以使用數組中的方法來操作children, 比如反轉或刪除

  React.cloneElement: 克隆children,  為什么要克隆呢?因為獲取到children 之后,它是只讀的,並不能給它添加屬性,如果想給children添加屬性,那只能先復制一份到組件中,然后再添加屬性,所以它接受兩個參數,一個是children, 要復制的children, 一個是對象,相要添加的給children的屬性。

  簡單寫幾個實例,熟悉一下這幾個方法的用法,在ThemeSelector 組件中添加一個下拉列表示框來選擇主題,來改變children ActionButton 和Message 的主題,使用是map 和cloneElement

import React, { Component } from 'react'

export default class ThemeSelector extends Component {
    state = { theme: 'primary' }

    setTheme = (event) => {
        console.log(event.target.value)
        this.setState({ theme: event.target.value });
    }

    render() {
// map 和cloneElement 的使用 let modChildren
= React.Children.map(this.props.children, (child) => { return React.cloneElement(child, { theme: this.state.theme }) }) return ( <div children='bg-dark p-2'> {/* 下拉列表,更改主題 */} <div className='form-group text-left'> <label className='text-white'>theme:</label> <select className='form-control' value={this.state.theme} onChange={this.setTheme}> <option value='primary'>primary</option> <option value='secondary'>secondary</option> <option value='success'>success</option> </select> </div> {/* map 和clone 后的children */} <div className="bg-info p-2"> {modChildren} </div> </div> ) } }

  map 的使用,由於chidren的屬性都是直讀的,所以不能直接使用forEach 來迭代添加屬性,所以使用map 來迭代,用React.cloneElemnet 來復制每一個child,同時給它添加屬性。cloneElement 只接受一個child component 和一個props 對象,這個props 對象會和child component 現有的屬性進行合並, 最終,就是每一個child 獲取到了theme 屬性。

  再在themeSeclector 組件的最下面加一個反轉,使用toArray()

{/* map 和clone 后的children */}
<div className="bg-info p-2">
    {modChildren}
</div>

{/* toArray(), 轉化為數組,然后使用數組的reserse 方法進行反轉 */}
<div className="bg-info p-2">
    {React.Children.toArray(this.props.children).reverse()}
</div>

  加一個p元素,使用count 方法,顯示有children的個數

 <p>{React.Children.count(this.props.children)}</p>

   還剩一個only, 只有一個children. 常用來判斷children 是不是一個函數。如果我們children 需要用戶提供一個函數的時候,它有可能提供多個元素,所以使用only 就會報錯。如果themeSelect的組件是

{this.props.children()} , 使用{React.children.only(this.props.children)()}

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM