2.12 Композиция вместо наследования
React имеет мощную модель композиции. Поэтому, чтобы повторно использовать код в компонентах, мы рекомендуем использовать именно её, а не наследование.
В этом разделе мы рассмотрим несколько проблем, возникающих, когда разработчики-новички тяготеют к наследованию. Также мы покажем, как их разрешить с помощью композиции.
Некоторые компоненты могут не знать заранее о своих потомках. Особенно это характерно для таких
компонентов, как боковое меню (Sidebar
) или диалог (Dialog
), которые
часто представляют собой общие контейнеры.
Мы рекомендуем, чтобы такие компоненты использовали специальное свойство children
.
Оно позволяет передавать дочерние элементы из вне непосредственно в результирующий вывод:
function Message(props) {
return (
<p className="message__content">
<h3 className="message__title">{props.title}</h3>
<p className="message__text">{props.children}</p>
</p>
);
}
С помощью этого свойства другие компоненты могут передавать произвольные потомки, используя JSX-вложение:
function SuccessMessage(props) {
return (
<div className={'message message_success'}>
<MessageContent title={props.title}>{props.children}</MessageContent>
</div>
);
}
function MessageContent(props) {
return (
<p className="message__content">
<h3 className="message__title">{props.title}</h3>
<p className="message__text">{props.children}</p>
</p>
);
}
function App(){
return (
<p>
<SuccessMessage title="Успех">Операция завершена успешно!</SuccessMessage>
</p>
);
}
Все, что находится внутри JSX-тега <SuccessMessage>
, передается в компонент
MessageContent
как свойство children
. Поскольку MessageContent
отрисовывает
{props.children}
внутри <p>
, в результирующем выводе отображаются
все элементы, переданные компоненту <SuccessMessage>
.
Иногда, хоть и не так часто, встречается ситуация, когда вам нужно иметь несколько «точек крепления»
элементов в компоненте. В таких случаях вы можете придумать свое собственное соглашение и
не использовать children
:
function BackButton(props) {
return (
<button className={'back-button'}>{props.children}</button>
);
}
function NavButton(props) {
return (
<button className={'nav-button'}>{props.children}</button>
);
}
function NavButtons(props) {
return (
<div className={'nav-buttons'}>{props.children}</div>
);
}
function NavBar(props) {
return (
<div className="nav-bar">
<div className="nav-bar__back-button-wrapper">{props.backButton}</div>
<div className="nav-bar__nav-buttons-wrapper">{props.navButtons}</div>
</div>
);
}
function App(){
let navButtons = (
<NavButtons>
<NavButton>Один</NavButton>
<NavButton>Два</NavButton>
<NavButton>Три</NavButton>
</NavButtons>
);
let backButton = (<BackButton>Назад</BackButton>);
return (
<p>
<NavBar backButton={backButton} navButtons={navButtons}/>
</p>
);
}
Элементы React, такие как <NavButtons />
и <BackButton />
, - являются
обычными объектами, поэтому вы можете передавать их в качестве свойств компонентов, как и любые другие данные.
Иногда мы рассматриваем компоненты как «частные случаи» других компонентов.
Например, мы можем сказать, что SuccessMessage
- это частный случай Message
.
В React это достигается композицией, где более «конкретный» компонент отображает более «общий» и настраивает его с помощью свойств:
function SuccessMessage(props) {
return (
<div className={'message message_success'}>
<Message title={props.title}>{props.children}</Message>
</div>
);
}
function Message(props) {
return (
<p className="message__content">
<h3 className="message__title">{props.title}</h3>
<p className="message__text">{props.children}</p>
</p>
);
}
Композиция работает точно также для компонентов, которые определены как классы.
В Facebook мы используем библиотеку React в тысячах компонентов. За это время мы не обнаружили ситуаций, где порекомендовали бы создавать иерархии наследования компонентов.
Свойства props
и композиция дают вам всю необходимую вам гибкость, чтобы индивидуально
подобрать внешний вид и поведение компонента. Помните, что компоненты могут принимать любые
свойства, включая примитивные значения, элементы React и функции.
Если вы хотите повторно использовать не относящийся к UI функционал между компонентами, предлагаем вынести его в отдельный модуль JavaScript. Компоненты смогут импортировать этот модуль и использовать ваш функционал (а также объект или класс), избегая наследования.