UI-элементы имеют иерархическую структуру. Например, компонент card в Bootstrap:
<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>
, так и к остальным компонентам Bootstrap, таким как модальные окна и навигация.
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
будет содержать массив.
Если внимательно посмотреть на документацию React, то можно увидеть следующее определение children: "children are an opaque data structure" (свойство children
– непрозрачная структура данных). Другими словами, нельзя однозначно полагаться на тип этого пропса, так как снаружи можно передать всё что угодно.
Подобное поведение может приводить к трудноотловимым ошибкам. Например, проверка this.props.children.length
— это не всегда количество детей. Если children
это одиночный элемент, например строка, то свойство length
вернет длину этой строки.
class MyComponent extends React.Component {
render() {
const { children } = this.props
return <p>Count: {children.length}</p>
}
}
// <p>Count: 4</p>
<MyComponent>Text</MyComponent>
Именно поэтому, React предоставляет набор функций предназначенных для манипулирования пропсом children
. Все они доступны в React.Children
. Эти функции знают про особенности работы children
, сами проверяют тип и делают нужные проверки в зависимости от типа данных.
React.Children.map()
See the Pen js_react_children_map by Hexlet (@hexlet) on CodePen.
Из урока про обработку коллекций вы можете вспомнить, что при работе со списком каждому элементу нужно установить проп key
. Если этого не сделать в случае с React.Children.map
, React не выбросит предупреждение. Так сделано намеренно, поскольку у потомков обычно нет уникальных идентификаторов.
React.Children.count()
Чтобы получить количество детей, нужно использовать метод React.Children.count()
. В него передается children
. Узлы, которые не являются DOM-элементами, будут проигнорированы:
class ChildrenCounter extends React.Component {
render() {
const { children } = this.props
return <p>Count: {React.Children.count(children)}</p>
}
}
// Count: 1
<ChildrenCounter>
Second!
</ChildrenCounter>
// Count: 2
<ChildrenCounter>
<p>First</p>
<ChildComponent />
</ChildrenCounter>
// Count: 2
<ChildrenCounter>
{() => <h1>First!</h1>} // будет пропущено, поскольку не является dom-элементом
Second!
<p>Third!</p>
</ChildrenCounter>
Кроме перечисленного выше, бывает необходимо обработать дочерние элементы перед выводом, изменив часть в пропсе. Конечно же, напрямую этого сделать нельзя, ведь пропсы неизменяемы. Такого поведения можно добиться, клонируя элементы функцией React.cloneElement()
.
Иерархия компонентов во многом закладывает структуру приложения. То, как вы распределите приложение на компоненты определит кодовую базу вашего проекта.
Есть несколько советов как разбивать приложение на компоненты:
Вам ответят команда поддержки Хекслета или другие студенты.
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт