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>
    );
  }
    

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


Все, что находится внутри 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>
    );
  }
    

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


Элементы 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. Компоненты смогут импортировать этот модуль и использовать ваш функционал (а также объект или класс), избегая наследования.