UI-элементы имеют иерархическую структуру. Например, компонент card в Бутстрапе:

<div class="card">
  <img class="card-img-top" src="..." alt="Card image cap">
  <div class="card-body">
    <h4 class="card-title">Card title</h4>
    <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
    <a href="#" class="btn btn-primary">Go somewhere</a>
  </div>
</div>

Блок карточки может содержать внутри себя картинку и тело. Тело в свою очередь может состоять из заголовка и текста, а текст может быть чем угодно. Не удивлюсь, если в теле карты получится разместить другие карты.

То же самое применимо как к элементарным элементам самого html, например, тегу div, так и к остальным компонентам Бутстрапа, таким как модальные окна и навигация.

html соответствует природе ui и естественным образом позволяет строить композиции элементов за счет вкладывания тегов друг в друга. Точно так же работает и jsx. Пока мы использовали этот факт только для встроенных компонентов. Теперь пришла пора попробовать реализовать подобное поведение в самописных компонентах. Возьмем alert из bootstrap.

See the Pen YxgWVm by Hexlet (@hexlet) on CodePen.

В примере выше обязательной частью является только основной div. Содержимое зависит от конкретной ситуации. Подставляется оно с помощью свойства children.

See the Pen js_react_children_alert_component by Hexlet (@hexlet) on CodePen.

Обратите внимание на то, что компонент стал использоваться как парный тег в jsx:

const vdom = <Alert>
  <p>Whenever you need to, be sure to use margin utilities to keep things nice and tidy.</p>
</Alert>;

Все, что находится между открывающим и закрывающим тегом, попадает внутрь свойства children.

Но будьте бдительны: тип данных свойства children зависит от содержимого. В простейшем случае, когда тег используется как одиночный <div />, это свойство будет равно undefined.

Если этим содержимым является строчка, то именно она окажется внутри children. Правда, после некоторой обработки. JSX удаляет концевые пробелы и пустые строки. Следующие примеры будут отображены одинаково:

<div>Hello World</div>

<div>
  Hello World
</div>

<div>
  Hello
  World
</div>

<div>

  Hello World
</div>

Любой одиночный дочерний компонент также будет представлен сам собой в children. Во всех остальных случаях children будет содержать массив.

Если внимательно посмотреть на документацию Реакта, то можно увидеть следующее определение children: "children are an opaque data structure" (свойство children - непрозрачная структура данных). Другими словами, нельзя однозначно полагаться на тип этого свойства, так как снаружи можно передать все что угодно.

Реакт предоставляет набор функций, предназначенных для манипулирования свойством children. Все они доступны в React.Children.

See the Pen js_react_children_map by Hexlet (@hexlet) on CodePen.

Точно так же, во избежании конфузов для определения количества элементов внутри children нужно пользоваться специализированной функцией Реакта. Например, у children со значением "Hello World!" длина будет 12. Совсем не то что мы ожидали.

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

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

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

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

Кроме перечисленного выше, бывает необходимо обработать детей перед выводом, изменив часть свойств. Конечно же, напрямую этого сделать нельзя, ведь свойства неизменяемы. Такого поведения можно добиться клонируя элементы функцией React.cloneElement.