2.12 Композиция вместо наследования

React имеет мощную модель композиции, и мы рекомендуем использовать композицию вместо наследования для повторного использования кода между компонентами.

В этом разделе мы рассмотрим несколько проблем, когда разработчики, являющиеся новичками в React, часто стремятся к наследованию, и покажем, как мы можем решить их с помощью композиции.


2.12.1 Вместимость

Некоторые компоненты могут не знать заранее о своих потомках. Это особенно характерно для таких компонентов, как боковое меню (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>
    );
  }
    

Посмотреть в CodePen


Все, что находится внутри JSX-тега <SuccessMessage>, передается в компонент MessageContent как свойство children. Поскольку MessageContent отрисовывает {props.children} внутри <p>, в результирующем выводе отображаются все переданные элементы.

Хотя это и менее распространено, иногда вам может понадобиться несколько «точек крепления» элементов в компоненте. В таких случаях вы можете придумать свое собственное соглашение и не использовать 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>
    );
  }
    

Посмотреть в CodePen


Элементы React, такие как <NavButtons /> и <BackButton />, - являются обычными объектами, поэтому вы можете передавать их как свойства компонентов, как и любые другие данные.


2.12.2 Специализация

Иногда мы рассматриваем компоненты как «частные случаи» других компонентов. Например, мы можем сказать, что 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>
    );
  }
    

Композиция работает одинаково хорошо для компонентов, определенных как классы.


2.12.3 Что насчет наследования?

В Facebook React используется в тысячах компонентов, но не было обнаружено каких-либо ситуаций, где разработчики рекомендовали бы создавать иерархии наследования компонентов.

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

Если вы хотите повторно использовать функциональность, не относящуюся к UI, между компонентами, предлагается извлечь её в отдельный модуль JavaScript. Компоненты могут импортировать его и использовать эту функциональность (а также объект или класс), не расширяя её.