对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帮助函数使咱们的生活更加简单。

文章同步于我的小站

相关文章
相关标签/搜索