對React children 的深刻理解

React的核心爲組件。你能夠像嵌套HTML標籤同樣嵌套使用這些組件,這使得編寫JSX更加容易由於它相似於標記語言。express

當我剛開始學習React時,當時我認爲「使用 props.children 就這麼回事,我知道它的一切」。我錯了。。api

由於咱們使用的事JavaScript,咱們會改變children。咱們可以給它們發送特殊的屬性,以此來決定它們是否進行渲染。讓咱們來探究一下React中children的做用。數組

子組件

咱們有一個組件 <Grid /> 包含了幾個組件 <Row /> 。你可能會這麼使用它:服務器

<Grid>
  <Row />
  <Row />
  <Row />
</Grid>

這三個 Row 組件都成爲了 Gridprops.children 。使用一個表達式容器,父組件就可以渲染它們的子組件:數據結構

class Grid extends React.Component {
  render() {
    return <div>{this.props.children}</div>
  }
}

父組件也可以決定不渲染任何的子組件或者在渲染以前對它們進行操做。例如,這個 <Fullstop /> 組件就沒有渲染它的子組件:app

class Fullstop extends React.Component {
  render() {
    return <h1>Hello world!</h1>
  }
}

無論你將什麼子組件傳遞給這個組件,它都只會顯示「Hello world!」函數

任何東西都能是一個child

React中的Children不必定是組件,它們可使任何東西。例如,咱們可以將上面的文字做爲children傳遞咱們的 <Grid /> 組件。學習

<Grid>Hello world!</Grid>

JSX將會自動刪除每行開頭和結尾的空格,以及空行。它還會把字符串中間的空白行壓縮爲一個空格。ui

這意味着如下的這些例子都會渲染出同樣的狀況:this

<Grid>Hello world!</Grid>

<Grid>
  Hello world!
</Grid>

<Grid>
  Hello
  world!
</Grid>

<Grid>

  Hello world!
</Grid>

你也能夠將多種類型的children完美的結合在一塊兒:

<Grid>
  Here is a row:
  <Row />
  Here is another row:
  <Row />
</Grid>

child 的功能

咱們可以傳遞任何的JavaScript表達式做爲children,包括函數。

爲了說明這種狀況,如下是一個組件,它將執行一個傳遞過來的做爲child的函數:

class Executioner extends React.Component {
  render() {
    // See how we're calling the child as a function?
    //                        ↓
    return this.props.children()
  }
}

你會像這樣的使用這個組件

<Executioner>
  {() => <h1>Hello World!</h1>}
</Executioner>

固然,這個例子並沒什麼用,只是展現了這個想法。

假設你想從服務器獲取一些數據。你能使用多種方法實現,像這種將函數做爲child的方法也是可行的。

<Fetch url="api.myself.com">
  {(result) => <p>{result}</p>}
</Fetch>

不要擔憂這些超出了你的腦容量。我想要的是當你之後遇到這種狀況時再也不驚訝。有了children什麼事都會發生。

操做children

若是你看過React的文檔你就會說「children是一個不透明的數據結構」。從本質上來說, props.children 可使任何的類型,好比數組、函數、對象等等。

React提供了一系列的函數助手來使得操做children更加方便。

循環

兩個最顯眼的函數助手就是 React.Children.map 以及 React.Children.forEach 。它們在對應數組的狀況下能起做用,除此以外,當函數、對象或者任何東西做爲children傳遞時,它們也會起做用。

class IgnoreFirstChild extends React.Component {
  render() {
    const children = this.props.children
    return (
      <div>
        {React.Children.map(children, (child, i) => {
          // Ignore the first child
          if (i < 1) return
          return child
        })}
      </div>
    )
  }
}

<IgnoreFirstChild /> 組件在這裏會遍歷全部的children,忽略第一個child而後返回其餘的。

<IgnoreFirstChild>
  <h1>First</h1>
  <h1>Second</h1> // <- Only this is rendered
</IgnoreFirstChild>

在這種狀況下,咱們也可使用 this.props.children.map 的方法。但要是有人講一個函數做爲child傳遞過來將會發生什麼呢?this.props.children 會是一個函數而不是一個數組,接着咱們就會產生一個error!

err

然而使用 React.Children.map 函數,不管什麼都不會報錯。

<IgnoreFirstChild>
  {() => <h1>First</h1>} // <- Ignored ?
</IgnoreFirstChild>

計數

由於this.props.children 能夠是任何類型的,檢查一個組件有多少個children是很是困難的。天真的使用 this.props.children.length ,當傳遞了字符串或者函數時程序便會中斷。假設咱們有個child:"Hello World!" ,可是使用 .length 的方法將會顯示爲12。

這就是爲何咱們有 React.Children.count 方法的緣由

class ChildrenCounter extends React.Component {
  render() {
    return <p>React.Children.count(this.props.children)</p>
  }
}

不管時什麼類型它都會返回children的數量

// Renders "1"
<ChildrenCounter>
  Second!
</ChildrenCounter>

// Renders "2"
<ChildrenCounter>
  <p>First</p>
  <ChildComponent />
</ChildrenCounter>

// Renders "3"
<ChildrenCounter>
  {() => <h1>First!</h1>}
  Second!
  <p>Third!</p>
</ChildrenCounter>

轉換爲數組

若是以上的方法你都不適合,你能將children轉換爲數組經過 React.Children.toArray 方法。若是你須要對它們進行排序,這個方法是很是有用的。

class Sort extends React.Component {
  render() {
    const children = React.Children.toArray(this.props.children)
    // Sort and render the children
    return <p>{children.sort().join(' ')}</p>
  }
}
<Sort>
  // We use expression containers to make sure our strings
  // are passed as three children, not as one string
  {'bananas'}{'oranges'}{'apples'}
</Sort>

上例會渲染爲三個排好序的字符串。

sort

執行單一child

若是你回過來想剛纔的 <Executioner /> 組件,它只能在傳遞單一child的狀況下使用,並且child必須爲函數。

class Executioner extends React.Component {
  render() {
    return this.props.children()
  }
}

咱們能夠試着去強制執行 propTypes ,就像下面這樣

Executioner.propTypes = {
  children: React.PropTypes.func.isRequired,
}

這會使控制檯打印出一條消息,部分的開發者將會把它忽視。相反的,咱們可使用在 render 裏面使用 React.Children.only

class Executioner extends React.Component {
  render() {
    return React.Children.only(this.props.children)()
  }
}

這樣只會返回一個child。若是不止一個child,它就會拋出錯誤,讓整個程序陷入中斷——完美的避開了試圖破壞組件的懶惰的開發者。

編輯children

咱們能夠將任意的組件呈現爲children,可是任然能夠用父組件去控制它們,而不是用渲染的組件。爲了說明這點,讓咱們舉例一個 可以擁有不少 RadioButton 組件的 RadiaGroup 組件。

RadioButtons 不會從 RadioGroup 自己上進行渲染,它們只是做爲children使用。這意味着咱們將會有這樣的代碼。

render() {
  return(
    <RadioGroup>
      <RadioButton value="first">First</RadioButton>
      <RadioButton value="second">Second</RadioButton>
      <RadioButton value="third">Third</RadioButton>
    </RadioGroup>
  )
}

這段代碼有一個問題。input 沒有被分組,致使了這樣:

爲了把 input 標籤弄到同組,必須擁有相同的name 屬性。固然咱們能夠直接給每一個RadioButtonname 賦值

<RadioGroup>
  <RadioButton name="g1" value="first">First</RadioButton>
  <RadioButton name="g1" value="second">Second</RadioButton>
  <RadioButton name="g1" value="third">Third</RadioButton>
</RadioGroup>

可是這個是無聊的而且容易出錯。咱們但是擁有JavaScript的全部功能的!

改變children的屬性

RadioGroup 中咱們將會添加一個叫作 renderChildren 的方法,在這裏咱們編輯children的屬性

class RadioGroup extends React.Component {
  constructor() {
    super()
    // Bind the method to the component context
    this.renderChildren = this.renderChildren.bind(this)
  }

  renderChildren() {
    // TODO: Change the name prop of all children
    // to this.props.name
    return this.props.children
  }

  render() {
    return (
      <div className="group">
        {this.renderChildren()}
      </div>
    )
  }
}

讓咱們開始遍歷children得到每一個child

renderChildren() {
  return React.Children.map(this.props.children, child => {
    // TODO: Change the name prop to this.props.name
    return child
  })
}

咱們如何編輯它們的屬性呢?

永恆地克隆元素

這是今天展現的最後一個輔助方法。顧名思義,React.cloneElement 會克隆一個元素。咱們將想要克隆的元素看成第一個參數,而後將想要設置的屬性以對象的方式做爲第二個參數。

const cloned = React.cloneElement(element, {
  new: 'yes!'
})

如今,clone 元素有了設置爲 "yes!" 的屬性 new

這正是咱們的 RadioGroup 所需的。咱們克隆全部的child而且設置name 屬性

renderChildren() {
  return React.Children.map(this.props.children, child => {
    return React.cloneElement(child, {
      name: this.props.name
    })
  })
}

最後一步就是傳遞一個惟一的 nameRadioGroup

<RadioGroup name="g1">
  <RadioButton value="first">First</RadioButton>
  <RadioButton value="second">Second</RadioButton>
  <RadioButton value="third">Third</RadioButton>
</RadioGroup>

沒有手動添加 name 屬性給全部的 RadioButton ,咱們只是告訴了 RadioGroup 所需的name而已。

總結

Children使React組件更像是標記而不是 脫節的實體。經過強大的JavaScript和一些React幫助函數使咱們的生活更加簡單。

文章同步於我的小站

相關文章
相關標籤/搜索