2.5 Компоненты и свойства

Компоненты позволяют вам разделить UI на независимые, переиспользуемые части и работать с каждой из них отдельно.

Концептуально, компоненты подобны JavaScript-функциям. Они принимают произвольные данные (называемые props) и возвращают React-элементы, описывающие что должно появиться на экране.


2.5.1 Компоненты-функции и компоненты-классы

Самый простой способ объявить компонент – это написать JavaScript-функцию:

Код
    
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    

Эта функция является валидным React-компонентом, потому что она принимает единственный объект props с данными как аргумент и возвращает React-элемент. Такие компоненты называются «функциональными», потому что они буквально являются JavaScript-функциями.

Другой способ объявить компонент заключается в использовании ES6-классов:

Код
    
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    

Два приведенных выше компонента являются эквивалентными с точки зрения React.

Классы имеют некоторые дополнительные возможности, которые будут рассматриваться в следующем разделе. До тех пор мы будем использовать функциональные компоненты за их краткость.


2.5.2 Отрисовка компонентов

Ранее, мы наталкивались лишь на React-элементы, которые представляют собой DOM-теги:

Код
    
    const element = <div/>;
    

Тем не менее, элементы могут представлять собой определенные пользователем компоненты:

Код
    
    const element = <Welcome name="Sara" />;
    

Когда React видит, что элемент представляет собой пользовательский компонент, он передает все JSX-атрибуты в этот компонент единым объектом. Такой объект называется props.

Например, этот код отрисовывает «Hello, Sara» на странице:

Код
    
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }

    const element = <Welcome name="Sara" />;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    

Попробовать в CodePen.

Давайте прорезюмируем, что произошло в этом примере:

  1. Мы вызвали ReactDOM.render() с элементом <Welcome name="Sara" />.
  2. React вызывает компонент Welcome с объектом {name: 'Sara'} в качестве свойств.
  3. Наш компонент Welcome возвращает элемент <h1>Hello, Sara</h1> как результат.
  4. React DOM эффективно обновляет DOM, чтобы соответствовать <h1>Hello, Sara</h1>

Внимание! Всегда именуйте свои компоненты с большой буквы.
Например, <div/> представляет собой DOM-тег, а <Welcome/> представляет собой компонент и требует, чтобы Welcome был в области видимости.

2.5.3 Композиция компонентов

Компоненты могут ссылаться на другие компоненты в своём выводе (результате отрисовки). Это позволяет нам использовать ту же самую абстракцию компонента для любого уровня детализации. Кнопка, форма, диалог, экран: в React-приложении все эти сущности выражены компонентами.

К примеру, мы можем создать компонент App, который отрисовывает Welcome много раз:

Код
    
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }

    function App() {
      return (
        <div>
          <Welcome name="Sara" />
          <Welcome name="Cahal" />
          <Welcome name="Edite" />
        </div>
      );
    }

    ReactDOM.render(
      <App />,
      document.getElementById('root')
    );
    

Попробовать в CodePen.

Как правило, новые React-приложения имеют единственный компонент App на самом верху иерархии. Тем не менее, если вы интегрируете React в уже существующее приложение, вы можете начать снизу вверх с маленького компонента, такого как Button и постепенно двигаться вверх по иерархии отображения.


2.5.4 Извлечение компонентов

Не бойтесь разделять компоненты на более маленькие компоненты.

Рассмотрим пример с компонентом Comment:

Код
    
    function Comment(props) {
      return (
        <div className="Comment">
          <div className="UserInfo">
            <img className="Avatar"
                 src={props.author.avatarUrl}
                 alt={props.author.name}
            />
            <div className="UserInfo-name">
              {props.author.name}
            </div>
          </div>
          <div className="Comment-text">
            {props.text}
          </div>
          <div className="Comment-date">
            {formatDate(props.date)}
          </div>
        </div>
      );
    }
    

Попробовать в CodePen.

Он принимает author (объект), text (строка) и date (дата) как свойства, и описывает комментарий на социальном медиа веб-сайте.

Данный компонент довольно сложно поддаётся именению из-за его вложенности. Также тяжело повторно использовать и его составные части. Давайте извлечем из него несколько небольших компонентов, упростив исходный компонент.

Для начала давайте извлечем из него компонент Avatar:

Код
    
    function Avatar(props) {
      return (
        <img className="Avatar"
             src={props.user.avatarUrl}
             alt={props.user.name}/>
      );
    }
    

Компонент Avatar не знает о том, что он отрисовывается внутри компонента Comment. Вот почему мы дали его свойству объекта props более общее имя: user, вместо author.

Совет! Мы рекомендуем именовать props с точки зрения компонента, а не контекста, в котором он будет использован.

Сейчас мы можем немного упростить компонент Comment:

Код
    
    function Comment(props) {
      return (
        <div className="Comment">
          <div className="UserInfo">
            <Avatar user={props.author} />
            <div className="UserInfo-name">
              {props.author.name}
            </div>
          </div>
          <div className="Comment-text">
            {props.text}
          </div>
          <div className="Comment-date">
            {formatDate(props.date)}
          </div>
        </div>
      );
    }
    

Далее, мы извлечем компонент UserInfo, который отрисовывает компонент Avatar рядом с именем пользователя:

Код
    
    function UserInfo(props) {
      return (
        <div className="UserInfo">
          <Avatar user={props.user} />
          <div className="UserInfo-name">
            {props.user.name}
          </div>
        </div>
      );
    }
    

Это позволяет нам еще больше упростить компонент Comment:

Код
    
    function Comment(props) {
      return (
        <div className="Comment">
          <UserInfo user={props.author} />
          <div className="Comment-text">
            {props.text}
          </div>
          <div className="Comment-date">
            {formatDate(props.date)}
          </div>
        </div>
      );
    }
    

Попробовать в CodePen.

Извлечение компонентов по началу может показаться рутинной работой, но наличие набора переиспользуемых компонентов окупается в больших приложениях.

Золотое правило: если какая-то часть вашего UI используется неоднократно (Button, Panel, Avatar), или довольно сложная (составная) (App, FeedStory, Comment) – она хороший кандидат на то, чтобы стать переиспользуемым компонентом

2.5.4 Свойства props – только для чтения

Будете ли вы объявлять компонент как функцию или класс, он никогда не должен модифицировать свои свойства props. Рассмотрим эту sum функцию:

Код
    
    function sum(a, b) {
      return a + b;
    }
    

Такие функции называются «чистыми». Потому что они не пытаются изменить свои аргументы и всегда возвращают одинаковый результат для тех же самых аргументов.

В противоположность им, следующая функция не является чистой, потому что она изменяет свои аргументы:

Код
    
    function withdraw(account, amount) {
      account.total -= amount;
    }
    

React является очень гибким, но он имеет одно строгое правило:

Все React-компоненты должны работать как чистые функции в отношении своих свойств “props”

Конечно, UI приложения – динамический и изменяется со временем. В следующем разделе мы познакомимся с новой концепцией «состояния». Состояние позволяет React-компонентам изменять их вывод со временем в ответ на действия пользователя, ответы сети или что-либо другое, не нарушая данное правило.