4 added
4 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Вы уже основательно изучили JSX и не интерактивный способ работы с компонентами React. С этого урока начинается самая главная часть: взаимодействие с пользователем. Ключевые понятия, которые будут рассмотрены в этом уроке: события и состояние. Пример:</p>
1
<p>Вы уже основательно изучили JSX и не интерактивный способ работы с компонентами React. С этого урока начинается самая главная часть: взаимодействие с пользователем. Ключевые понятия, которые будут рассмотрены в этом уроке: события и состояние. Пример:</p>
2
-
<p><a>https://codepen.io/hexlet/pen/ZJPpog</a></p>
2
+
<p><a>Попрактиковаться</a></p>
3
<p>Компоненты, которые создавались в курсе раньше, были stateless, то есть не содержали никакого состояния и могли только отрисовывать переданные свойства. Компонент в примере выше является stateful, так как сохраняет внутри себя состояние текущего времени. По порядку:</p>
3
<p>Компоненты, которые создавались в курсе раньше, были stateless, то есть не содержали никакого состояния и могли только отрисовывать переданные свойства. Компонент в примере выше является stateful, так как сохраняет внутри себя состояние текущего времени. По порядку:</p>
4
<ol><li><p>Внутри компонента, в конструкторе, определяется начальное состояние, с которым будет инициализирован компонент после отрисовки. Единственное требование к состоянию, которое предъявляет React - тип данных: он должен быть объектом. То, что хранится внутри, определяется самим приложением.</p>
4
<ol><li><p>Внутри компонента, в конструкторе, определяется начальное состояние, с которым будет инициализирован компонент после отрисовки. Единственное требование к состоянию, которое предъявляет React - тип данных: он должен быть объектом. То, что хранится внутри, определяется самим приложением.</p>
5
<p>Способ задания начального состояния выглядит так:</p>
5
<p>Способ задания начального состояния выглядит так:</p>
6
<p>Обратите внимание на то, что это единственное место, где<em>state</em>может изменяться напрямую (точнее, создаваться). Во всех остальных местах this.state должен использоваться<em>только</em>для чтения! Подробнее об этом дальше.</p>
6
<p>Обратите внимание на то, что это единственное место, где<em>state</em>может изменяться напрямую (точнее, создаваться). Во всех остальных местах this.state должен использоваться<em>только</em>для чтения! Подробнее об этом дальше.</p>
7
</li>
7
</li>
8
<li><p>Функция render использует данные из<em>state</em>для отрисовки. Здесь никаких сюрпризов.</p>
8
<li><p>Функция render использует данные из<em>state</em>для отрисовки. Здесь никаких сюрпризов.</p>
9
</li>
9
</li>
10
<li><p>На кнопку вешается обработчик на клик. В отличие от HTML, в свойство onClick передается функция и она вызовется автоматически в момент срабатывания события. Внутри обработчика определяется текущая дата и идет установка нового состояния. Ещё раз: крайне важно не изменять<em>state</em>напрямую. Для установки нового состояния в React предусмотрена функция setState. Именно её вызов приводит к тому, что компонент, в конце концов, перерисуется. Происходит это не сразу, то есть setState работает асинхронно и внутренняя магия пытается оптимизировать процесс рисования.</p>
10
<li><p>На кнопку вешается обработчик на клик. В отличие от HTML, в свойство onClick передается функция и она вызовется автоматически в момент срабатывания события. Внутри обработчика определяется текущая дата и идет установка нового состояния. Ещё раз: крайне важно не изменять<em>state</em>напрямую. Для установки нового состояния в React предусмотрена функция setState. Именно её вызов приводит к тому, что компонент, в конце концов, перерисуется. Происходит это не сразу, то есть setState работает асинхронно и внутренняя магия пытается оптимизировать процесс рисования.</p>
11
</li>
11
</li>
12
</ol><p>Ещё один важный момент заключается в том, как определена функция handleClick. Так как происходит работа с классом, то логично было бы использовать такой стиль определения:</p>
12
</ol><p>Ещё один важный момент заключается в том, как определена функция handleClick. Так как происходит работа с классом, то логично было бы использовать такой стиль определения:</p>
13
<p>Но такой подход плохо работает в React по двум причинам.</p>
13
<p>Но такой подход плохо работает в React по двум причинам.</p>
14
<p>Первая заключается в том, что обработчики вызываются асинхронно, а методы в классах - это обычные функции с поздним связыванием. Поэтому нельзя просто так повесить обработчик, так как он потеряет this. С таким определением придется постоянно писать подобный код: onClick={this.handleClick.bind(this)} либо такой onClick={() => this.handleClick()}.</p>
14
<p>Первая заключается в том, что обработчики вызываются асинхронно, а методы в классах - это обычные функции с поздним связыванием. Поэтому нельзя просто так повесить обработчик, так как он потеряет this. С таким определением придется постоянно писать подобный код: onClick={this.handleClick.bind(this)} либо такой onClick={() => this.handleClick()}.</p>
15
<p>Вторая причина связана с производительностью. Оба предыдущих примера передачи обработчика порождают при каждом вызове функции render новые обработчики (так как функции сравниваются по ссылкам, а не по содержимому), а для React это критично. Поэтому правильный способ определения - стрелочная функция:</p>
15
<p>Вторая причина связана с производительностью. Оба предыдущих примера передачи обработчика порождают при каждом вызове функции render новые обработчики (так как функции сравниваются по ссылкам, а не по содержимому), а для React это критично. Поэтому правильный способ определения - стрелочная функция:</p>
16
<p>Ещё один пример:</p>
16
<p>Ещё один пример:</p>
17
-
<p><a>https://codepen.io/hexlet/pen/RwgpZbz</a></p>
17
+
<p><a>Попрактиковаться</a></p>
18
<p>Логично ожидать, что счётчик будет увеличиваться на 2 при каждом клике, но этого не происходит. Как уже говорилось выше, this.setState выполняется Реактом не сразу. Следовательно, может возникнуть ситуация, когда state или props изменились к тому моменту, когда будет выполняться изменение состояния. Для таких случаев, когда новое состояние определяется на основе предыдущего (или на основе пропсов) у setState предусмотрена возможность принимать функцию вместо объекта:</p>
18
<p>Логично ожидать, что счётчик будет увеличиваться на 2 при каждом клике, но этого не происходит. Как уже говорилось выше, this.setState выполняется Реактом не сразу. Следовательно, может возникнуть ситуация, когда state или props изменились к тому моменту, когда будет выполняться изменение состояния. Для таких случаев, когда новое состояние определяется на основе предыдущего (или на основе пропсов) у setState предусмотрена возможность принимать функцию вместо объекта:</p>
19
<p>Первым параметром функция принимает состояние, а вторым пропсы. Такой подход позволяет получить доступ ко всему состоянию во время его обновления. Это бывает полезно, когда новое состояние зависит от текущего, как в примере выше, где к предыдущему состоянию прибавляется 1.</p>
19
<p>Первым параметром функция принимает состояние, а вторым пропсы. Такой подход позволяет получить доступ ко всему состоянию во время его обновления. Это бывает полезно, когда новое состояние зависит от текущего, как в примере выше, где к предыдущему состоянию прибавляется 1.</p>
20
<p>Попробуйте переписать пример со счётчиком, используя описанную форму setState.</p>
20
<p>Попробуйте переписать пример со счётчиком, используя описанную форму setState.</p>
21
<p>По большому счёту, описанные выше механизмы открывают практически все двери. Теперь вы с лёгкостью можете создавать интерактивные компоненты и оживлять ваш UI. Всё остальное - это тонкости, предусмотренные для различных ситуаций.</p>
21
<p>По большому счёту, описанные выше механизмы открывают практически все двери. Теперь вы с лёгкостью можете создавать интерактивные компоненты и оживлять ваш UI. Всё остальное - это тонкости, предусмотренные для различных ситуаций.</p>
22
<h2>Инициализация</h2>
22
<h2>Инициализация</h2>
23
<p>Представьте, что в компоненте, созданном выше, нужно инициализировать счётчик со свойством count, переданным снаружи. И только в его отсутствие ставить 0. Для решения этой задачи нужно добавить две вещи:</p>
23
<p>Представьте, что в компоненте, созданном выше, нужно инициализировать счётчик со свойством count, переданным снаружи. И только в его отсутствие ставить 0. Для решения этой задачи нужно добавить две вещи:</p>
24
<ol><li>Использовать свойство count как начальное значение счетчика.</li>
24
<ol><li>Использовать свойство count как начальное значение счетчика.</li>
25
<li>Добавить значение по умолчанию для свойства count.</li>
25
<li>Добавить значение по умолчанию для свойства count.</li>
26
-
</ol><p><a>https://codepen.io/hexlet/pen/ZJPeeZ</a></p>
26
+
</ol><p><a>Попрактиковаться</a></p>
27
<h2>setState</h2>
27
<h2>setState</h2>
28
<p>В следующем примере реализованы две кнопки, каждая из которых управляет своим состоянием.</p>
28
<p>В следующем примере реализованы две кнопки, каждая из которых управляет своим состоянием.</p>
29
-
<p><a>https://codepen.io/hexlet/pen/YxgZxN</a></p>
29
+
<p><a>Попрактиковаться</a></p>
30
<p>В данном примере объект состояния включает два свойства: count для одной кнопки и primary для другой. Основная хитрость этого примера заключается в процессе обновления состояния:</p>
30
<p>В данном примере объект состояния включает два свойства: count для одной кнопки и primary для другой. Основная хитрость этого примера заключается в процессе обновления состояния:</p>
31
<p>Функция setState заменяет значения ключей в предыдущем состоянии на значения этих же ключей в новом состоянии. То, что внутри функции не возвращалось, не трогается. Фактически происходит слияние старого состояния и нового. На практике это поведение крайне удобно, иначе пришлось бы каждый раз выполнять работу по слиянию руками.</p>
31
<p>Функция setState заменяет значения ключей в предыдущем состоянии на значения этих же ключей в новом состоянии. То, что внутри функции не возвращалось, не трогается. Фактически происходит слияние старого состояния и нового. На практике это поведение крайне удобно, иначе пришлось бы каждый раз выполнять работу по слиянию руками.</p>
32
<h2>Структура объекта состояния</h2>
32
<h2>Структура объекта состояния</h2>
33
<p>Существует множество способов организации данных внутри состояния. Скорее всего, вы захотите хранить их как-то так:</p>
33
<p>Существует множество способов организации данных внутри состояния. Скорее всего, вы захотите хранить их как-то так:</p>
34
<p>При таком подходе сущности, зависимые от других, находятся внутри. Если брать пример выше, то это означает, что каждый пост содержит внутри себя как автора, так и список комментариев, а каждый комментарий, в свою очередь, содержит внутри свои связанные сущности того же автора. При таком подходе получается, что состояние представляет собой дерево зависимостей. Хотя этот способ организации кажется вполне естественным, работать с ним крайне тяжело. Во-первых, одни и те же данные начнут дублироваться в разных местах и вам придётся синхронизировать изменения в них, что создает космические проблемы на пустом месте. Во-вторых, обновления таких данных (особенно в неизменяемом стиле) становятся сложными и многословными. В-третьих, так как все состояние - это один большой кусок, то любое обновление приведет к его полному копированию, что может быть дорогой операцией (в зависимости от размера состояния и количества обновлений в единицу времени).</p>
34
<p>При таком подходе сущности, зависимые от других, находятся внутри. Если брать пример выше, то это означает, что каждый пост содержит внутри себя как автора, так и список комментариев, а каждый комментарий, в свою очередь, содержит внутри свои связанные сущности того же автора. При таком подходе получается, что состояние представляет собой дерево зависимостей. Хотя этот способ организации кажется вполне естественным, работать с ним крайне тяжело. Во-первых, одни и те же данные начнут дублироваться в разных местах и вам придётся синхронизировать изменения в них, что создает космические проблемы на пустом месте. Во-вторых, обновления таких данных (особенно в неизменяемом стиле) становятся сложными и многословными. В-третьих, так как все состояние - это один большой кусок, то любое обновление приведет к его полному копированию, что может быть дорогой операцией (в зависимости от размера состояния и количества обновлений в единицу времени).</p>
35
<p>Общая рекомендация, которую дают разработчики React, это делать структуру максимально плоской, похожей на то, как хранятся данные в базе данных. Причём желательно в хорошо нормализованном виде. Другими словами, не нужно дублировать данные в состоянии. Пример того как правильно это делать:</p>
35
<p>Общая рекомендация, которую дают разработчики React, это делать структуру максимально плоской, похожей на то, как хранятся данные в базе данных. Причём желательно в хорошо нормализованном виде. Другими словами, не нужно дублировать данные в состоянии. Пример того как правильно это делать:</p>
36
36