react的核心是组件, 使得你可以利用jsx像嵌套HTML标签一样…


react的核心是组件, 使得你可以利用jsx像嵌套HTML标签一样去使用这些组件。 然而在包含jsx的表达式中, props.children一直贯穿其中。 当你开始使用React时, 你一定认为props.children也就那么回事, 但是你错了

1. Child components

子组件

父组件 <MyContainer /> 包含了几个子组件 <MyComponent />

<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

这三个 Component 组件都成为了 MyContainer 的 props.children 。 使用一个表达式容器,父组件就能够渲染它们的子组件:

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

当然, 父组件可以决定不渲染任何的子组件或者在渲染之前对它们进行操作

// 不管你将什么子组件传递给这个组件,它都只会显示“Hello world!”
class OnlySayHellow extends React.Component {
  render() {
    return <h1>Hello world!</h1>
  }
}

2.child 可以是任何东西

React 中的 Children不一定是组件,它们可以是任何东西

JSX表达式、JavaScript Expressions、Functions、Booleans, Null, and Undefined

JSX表达式

// component + string
<MyContainer>
  Here is a component:
  <MyFirstComponent />
  Here is another component:
  <MySecondComponent />
</MyContainer>

注意 JSX将会自动删除每行开头和结尾的空格,以及空行。它还会把字符串中间的空白行压缩为一个空格

// 如下代码结果一致

// code 1
<MyComponent>say hellow!</MyComponent>

// code 2
<MyComponent>
    say hellow!
</MyComponent>

// code 3
<MyComponent>
    say
    hellow!
</MyComponent>

// code 4
<MyComponent>

    say hellow!
</MyComponent>

JavaScript Expressions

function Item(props) {
  return <li>{props.message}</li>;
}

function TodoList() {
  const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  return (
    <ul>
      {todos.map((message) => <Item key={message} message={message} />)}
    </ul>
  );
}

Functions

例如,如果你有一个自定义组件,你可以把它当作回调来做。

// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
  let items = [];
  for (let i = 0; i < props.numTimes; i++) {
    items.push(props.children(i));
  }
  return <div>{items}</div>;
}

function ListOfTenThings() {
  return (
    <Repeat numTimes={10}>
      {(index) => <div key={index}>This is item {index} in the list</div>}
    </Repeat>
  );
}

Children passed to a custom component can be anything, as long as that component transforms them into something React can understand before rendering. This usage is not common, but it works if you want to stretch what JSX is capable of.

传递给自定义组件的孩子可以是任何东西,只要组件将它们转换成某种反应就可以在呈现之前理解。 这种用法并不常见,但如果您想拉伸JSX所能使用的,它就可以工作

Booleans, Null, and Undefined Are Ignored

false, null, undefined, and true are valid children. They simply don’t render.

false, null, undefined, 和 true都是有效的子代。他们只是不渲染。这些JSX表达式将全部呈现为 同一事物

// bad code
<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

// good code
<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

One caveat is that some “falsy” values, such as the 0 number, are still rendered by React. For example, this code will not behave as you might expect because 0 will be printed when props.messages is an empty array. To fix this, make sure that the expression before && is always bool

为了处理 this.props.children 这个封闭的数据结构, React提供了一个顶层的API.

3. React.Children.map

循环

在对应数组的情况下能起作用,除此之外,当函数、对象或者任何东西作为children传递时, 也会起作用

object React.Children.map(object children, function fn [, object context])

// 使用方法:
React.Children.map(this.props.children, function (child) {
    return <li>{child}</li>;
})

// 其他方法
this.props.children.forEach(function (child) {
    return <li>{child}</li>
})

注意

this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ; 如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。 所以,处理 this.props.children 的时候要小心

demo

<IgnoreFirstChild />  组件在这里会遍历所有的children,忽略第一个child然后返回其他的
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>
  <h1>First</h1>
  <h1>Second</h1> // <- Only this is rendered
</IgnoreFirstChild>

在这种情况下,我们也可以使用 this.props.children.map 的方法。但是假如 this.props.children 是一个函数而不是一个数组,接着我们就会产生一个error! “this.props.children.map is not a function”

React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object, 甚至是function. 使用 React.Children.map 函数,无论什么都不会报错

4. React.Children.forEach

循环 类似于 React.Children.map(), 但是不返回对象

React.Children.forEach(object children, function fn [, object context])

5. React.Children.count

计数 因为this.props.children 可以是任何类型的,检查一个组件有多少个children是非常困难的

object React.Children.only(object children)

不要天真的以为, 可以使用this.props.children.length, 当传递的是字符串或者 函数时程序中断可能会让你哭!

假设我们有个child:"say hello!" ,但是使用 .length 的方法将会显示为10。

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

<ChildrenCounter>
    {() => <div>First!</div>}
    <p>Second!</p>
    <p>Third!</p>
</ChildrenCounter>

注意: 此处console结果可能为2或者3, 有的地方会报错 “Functions are not valid as a React child”, 此时count为2, 若不报错, 则为3

6. React.Children.toArray

转换为数组 可以通过 React.Children.toArray 方法将children转换为数组, 如果你 需要对它们进行排序,这个方法是非常有用的

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>
// 渲染为三个排好序的字符串
apples bananas oranges

7. React.Children.only

执行单一child

object React.Children.only(object children)

返回 children 中 仅有的子级, 否则抛出异常。这里仅有的子级, only方法接受的参数只能 是一个对象,不能是多个对象(数组)

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

这样只会返回一个child。如果不止一个child,它就会抛出错误,让整个程序陷入中断——完美 的避开了试图破坏组件的懒惰的开发者

8. renderChildren

改变children的属性

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

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

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

9. React.cloneElement

永恒地克隆元素

给每一个child增加一个name属性

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

这个场景就是RadioGroup的实现原理

<RadioGroup name="sex">
  <Radio value="boy">First</Radio>
  <Radio value="girl">Second</Radio>
</RadioGroup>

传递一个唯一的 name 给RadioGroup, 相当于给其内每一个radio增加name属性, 属于同一组


参考


最后, 希望大家早日实现:成为编程高手的伟大梦想!
欢迎交流~

微信公众号

本文版权归原作者曜灵所有!未经允许,严禁转载!对非法转载者, 原作者保留采用法律手段追究的权利!
若需转载,请联系微信公众号:连先生有猫病,可获取作者联系方式!