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'} в качестве свойств props.
  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-компонентам изменять их вывод со временем в ответ на действия пользователя, ответы сети или что-то другое, не нарушая данное правило.