Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Состояние

Мы основательно изучили JSX и неинтерактивный способ работы с компонентами React. С этого урока начинается самая главная часть: взаимодействие с пользователем. Ключевые понятия, которые будут рассмотрены в этом уроке: события и состояние. Начнём с примера:

See the Pen js_react_state_example by Hexlet (@hexlet) on CodePen.

Компоненты, которые мы создавали раньше, были stateless, то есть не содержали никакого состояния и могли только отрисовывать переданные свойства. Компонент в примере выше является stateful, так как сохраняет внутри себя состояние счетчика. По порядку:

  1. Внутри компонента, в конструкторе, определяется начальное состояние, с которым будет инициализирован компонент после отрисовки. Единственное требование к состоянию, которое предъявляет React - тип данных: он должен быть объектом. То, что хранится внутри, определяется самим приложением.

    Способ задания начального состояния выглядит так:

    class Component extends React.Component {
      constructor(props) {
        super(props); // всегда обязательно
        this.state = { count: 0 };
      }
    }
    

    Обратите внимание на то, что это единственное место, где state может изменяться напрямую (точнее, создаваться). Во всех остальных местах this.state должен использоваться только для чтения! Подробнее об этом дальше.

  2. Функция render использует данные из state для отрисовки. Здесь никаких сюрпризов.

  3. На кнопку вешается обработчик на клик. В отличие от HTML, в свойство onClick передается функция и она вызовется автоматически в момент срабатывания события. Внутри обработчика читается текущее значение счетчика, к нему прибавляется единица и далее идет установка нового состояния. Повторюсь: крайне важно не изменять state напрямую. Для установки нового состояния в React предусмотрена функция setState. Именно её вызов приводит к тому, что компонент, в конце концов, перерисуется. Происходит это не сразу, то есть setState работает асинхронно и внутренняя магия пытается оптимизировать процесс рисования.

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

class Counter extends React.Component {
  handleClick() {
    const { count } = this.state;
    this.setState({ count: count + 1 });
  };
}

Но такой подход плохо работает в React по двум причинам.

Первая заключается в том, что обработчики вызываются асинхронно, а методы в классах — это обычные функции с поздним связыванием. Поэтому мы не можем просто так повесить обработчик, так как он потеряет this. С таким определением придется постоянно писать подобный код: onClick={this.handleClick.bind(this)} либо такой onClick={() => this.handleClick()}.

Вторая причина связана с производительностью. Оба предыдущих примера передачи обработчика порождают при каждом вызове функции render новые обработчики (так как функции сравниваются по ссылкам, а не по содержимому), а для React это критично. Поэтому правильный способ определения - стрелочная функция:

class Counter extends React.Component {
  handleClick = () => {
    const { count } = this.state;
    this.setState({ count: count + 1 });
  };
}

Подробнее о производительности поговорим позже.

По большому счёту, описанный выше механизм открывает практически все двери. Теперь вы с лёгкостью можете создавать интерактивные компоненты и оживлять ваш UI. Всё остальное — это тонкости, предусмотренные для различных ситуаций.

Инициализация

Предположим, что в компоненте, созданном выше, нужно инициализировать счетчик со свойством count, переданным снаружи. И только в его отсутствие ставить 0. Для решения этой задачи нужно добавить две вещи:

  1. Использовать свойство count как начальное значение счетчика.
  2. Добавить значение по умолчанию для свойства count.

See the Pen js_react_state_init by Hexlet (@hexlet) on CodePen.

setState

Усложним компонент и реализуем две кнопки, каждая из которых управляет своим состоянием.

See the Pen js_react_state_set by Hexlet (@hexlet) on CodePen.

В данном примере объект состояния включает два свойства: count для одной кнопки и primary для другой. Основная хитрость этого примера заключается в процессе обновления состояния:

// первая кнопка
this.setState({ count: count + 1 });

// вторая кнопка
this.setState({ primary: !primary });

Функция setState не просто принимает на вход новое состояние. Она заменяет значения ключей в предыдущем состоянии на значения этих же ключей в новом состоянии. То, что передано не было - не трогается. То есть, в нашем случае мы передавали только то, что изменяли. На практике это поведение крайне удобно, иначе пришлось бы каждый раз выполнять работу по слиянию старого состояния с новым руками.

Структура объекта состояния

Существует множество способов организации данных внутри состояния. Скорее всего, вы захотите хранить их как-то так:

const blogPosts = [
  {
    id : "post1",
    author : {username : "user1", name : "User 1"},
    body : "......",
    comments : [
      {
        id : "comment1",
        author : {username : "user2", name : "User 2"},
        comment : ".....",
      },
      {
        id : "comment2",
        author : {username : "user3", name : "User 3"},
        comment : ".....",
      }
    ]
  },
  {
    id : "post2",
    author : {username : "user2", name : "User 2"},
    body : "......",
    comments : [
      {
        id : "comment3",
        author : {username : "user3", name : "User 3"},
        comment : ".....",
      },
    ]
  }
  // and repeat many times
]

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

Общая рекомендация, которую дают разработчики React, это делать структуру максимально плоской, похожей на то, как мы храним данные в базе данных. Причём желательно в хорошо нормализованном виде. Другими словами, не нужно дублировать данные в состоянии. Пример того как правильно это делать:

const state = {
  articles: [/*...*/],
  comments: [/*...*/],
}

<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу нужна профессиональная подписка

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

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы соглашаетесь c «Политикой конфиденциальности» и «Условиями оказания услуг».