HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>В разработке приложений на React часто возникают ситуации, когда компоненты отрисовываются без необходимости. Чаще всего это происходит при отрисовке родительского компонента или изменении пропсов.</p>
1 <p>В разработке приложений на React часто возникают ситуации, когда компоненты отрисовываются без необходимости. Чаще всего это происходит при отрисовке родительского компонента или изменении пропсов.</p>
2 <p>Лишняя перерисовка компонентов может привести к непредвиденным событиям, когда при отрисовке компонента срабатывают побочные эффекты, либо перерисовка может повлиять на производительность.</p>
2 <p>Лишняя перерисовка компонентов может привести к непредвиденным событиям, когда при отрисовке компонента срабатывают побочные эффекты, либо перерисовка может повлиять на производительность.</p>
3 <p>Самый простой способ отследить перерисовку компонента - это расставить точки остановки выполнения кода в дебагере, либо добавить вывод лога.</p>
3 <p>Самый простой способ отследить перерисовку компонента - это расставить точки остановки выполнения кода в дебагере, либо добавить вывод лога.</p>
4 <p>Ниже простое приложение из двух компонентов:</p>
4 <p>Ниже простое приложение из двух компонентов:</p>
5 <p>В компонент Greeting мы добавили вывод в console.log() с меткой о времени для наглядности. Так мы сможем отследить время отрисовки компонента. Компонент App использует в представлении Greeting и содержит поле ввода, в котором меняется состояние компонента.</p>
5 <p>В компонент Greeting мы добавили вывод в console.log() с меткой о времени для наглядности. Так мы сможем отследить время отрисовки компонента. Компонент App использует в представлении Greeting и содержит поле ввода, в котором меняется состояние компонента.</p>
6 <p>Если ввести значение в input, то в консоли браузера мы увидим вывод сообщения об отрисовке компонента Greeting. Компонент будет отрисовываться каждый раз, когда мы вводим значение в поле. Добавляем или удаляем один символ - и компонент перерисовывается. Если мы напечатаем слово hello, компонент перерисуется пять раз.</p>
6 <p>Если ввести значение в input, то в консоли браузера мы увидим вывод сообщения об отрисовке компонента Greeting. Компонент будет отрисовываться каждый раз, когда мы вводим значение в поле. Добавляем или удаляем один символ - и компонент перерисовывается. Если мы напечатаем слово hello, компонент перерисуется пять раз.</p>
7 <h2>Расширение Profiler</h2>
7 <h2>Расширение Profiler</h2>
8 <p>Есть более удобный способ отслеживать отрисовку компонентов. Для этого нужно установить расширения для браузера<a>React Developer Tools</a>. Расширение добавляет несколько инструментов для работы с React-приложением. Один из них - это Profiler, позволяющий отслеживать отрисовку компонентов.</p>
8 <p>Есть более удобный способ отслеживать отрисовку компонентов. Для этого нужно установить расширения для браузера<a>React Developer Tools</a>. Расширение добавляет несколько инструментов для работы с React-приложением. Один из них - это Profiler, позволяющий отслеживать отрисовку компонентов.</p>
9 <p>Нужно открыть вкладку Profiler в devtools, нажать кнопку записи слева и после этого взаимодействовать с приложением - например, ввести в поле любое значение, после этого остановить запись</p>
9 <p>Нужно открыть вкладку Profiler в devtools, нажать кнопку записи слева и после этого взаимодействовать с приложением - например, ввести в поле любое значение, после этого остановить запись</p>
10 <p>Так будет выглядеть результат, если ввести в поле строку "hello":</p>
10 <p>Так будет выглядеть результат, если ввести в поле строку "hello":</p>
11 <p>В правой верхней части можно переключаться между шагами рендера, Profiler сохраняет информацию о каждом рендере и отображает информацию сколько времени отрисовывался каждый компонент.</p>
11 <p>В правой верхней части можно переключаться между шагами рендера, Profiler сохраняет информацию о каждом рендере и отображает информацию сколько времени отрисовывался каждый компонент.</p>
12 <p>Пока может показаться, что это немного. Но в сложных приложениях с большим количеством компонентов такой инструмент может быть очень полезным.</p>
12 <p>Пока может показаться, что это немного. Но в сложных приложениях с большим количеством компонентов такой инструмент может быть очень полезным.</p>
13 <p>Обычно при сборке React-приложение оптимизируется и поддержка профайлера из него убирается. Но вы можете использовать флаг --profile в команде сборки, если используете create-react-app, чтобы оставить поддержку инструмента на сервере:</p>
13 <p>Обычно при сборке React-приложение оптимизируется и поддержка профайлера из него убирается. Но вы можете использовать флаг --profile в команде сборки, если используете create-react-app, чтобы оставить поддержку инструмента на сервере:</p>
14 <h2>Инструмент memo</h2>
14 <h2>Инструмент memo</h2>
15 <p>В нашем приложении изменяется состояние корневого компонента. Это влечет за собой его перерисовку, что, в свою очередь, влечет за собой перерисовку всех дочерних компонентов. Компонент Greeting каждый раз перерисовывается. Это не всегда нужно, ведь в компоненте каждый раз рендерится одно и то же сообщение.</p>
15 <p>В нашем приложении изменяется состояние корневого компонента. Это влечет за собой его перерисовку, что, в свою очередь, влечет за собой перерисовку всех дочерних компонентов. Компонент Greeting каждый раз перерисовывается. Это не всегда нужно, ведь в компоненте каждый раз рендерится одно и то же сообщение.</p>
16 <p>Такое поведение можно изменить, в React для этого используется инструмент мемоизации memo. Достаточно в него передать наш компонент:</p>
16 <p>Такое поведение можно изменить, в React для этого используется инструмент мемоизации memo. Достаточно в него передать наш компонент:</p>
17 <p>Вот как будет выглядеть наше приложение:</p>
17 <p>Вот как будет выглядеть наше приложение:</p>
18 <p>Теперь, если ввести в поле значение, то по профайлеру или выводу логов мы увидим, что компонент Greeting не отрисовывается.</p>
18 <p>Теперь, если ввести в поле значение, то по профайлеру или выводу логов мы увидим, что компонент Greeting не отрисовывается.</p>
19 <p>Это не значит, что компонент теперь всегда будет отрисовываться один раз. Если мы будем передавать в компонент измененный пропс, то перерисовка произойдет все равно:</p>
19 <p>Это не значит, что компонент теперь всегда будет отрисовываться один раз. Если мы будем передавать в компонент измененный пропс, то перерисовка произойдет все равно:</p>
20 <h2>Мемоизация и useMemo</h2>
20 <h2>Мемоизация и useMemo</h2>
21 <p>Изменение пропсов может привести к ситуации, когда сами данные не меняются, но меняется ссылка на объект. В этом случае перерисовка произойдет, и memo не поможет. Немного модифицируем наше приложение для демонстрации такой ситуации:</p>
21 <p>Изменение пропсов может привести к ситуации, когда сами данные не меняются, но меняется ссылка на объект. В этом случае перерисовка произойдет, и memo не поможет. Немного модифицируем наше приложение для демонстрации такой ситуации:</p>
22 <p>В приложение добавлен компонент Button, который принимает функцию для события onClick. Несмотря на то, что компонент мемоизирован, он все равно будет перерисовываться при каждой отрисовке App. Это происходит, потому что в компонент передается функция buttonHandler(). При каждой отрисовке App создается новая функция. React видит изменение ссылки на функцию и вызывает перерисовку компонента Button.</p>
22 <p>В приложение добавлен компонент Button, который принимает функцию для события onClick. Несмотря на то, что компонент мемоизирован, он все равно будет перерисовываться при каждой отрисовке App. Это происходит, потому что в компонент передается функция buttonHandler(). При каждой отрисовке App создается новая функция. React видит изменение ссылки на функцию и вызывает перерисовку компонента Button.</p>
23 <p>Чтобы избежать ненужных перерисовок компонента Button, нужно использовать хук useMemo. Он принимает функцию, которая возвращает какой-то результат. Хук запоминает этот результат и возвращает его каждый раз, не вызывая повторно переданную функцию.</p>
23 <p>Чтобы избежать ненужных перерисовок компонента Button, нужно использовать хук useMemo. Он принимает функцию, которая возвращает какой-то результат. Хук запоминает этот результат и возвращает его каждый раз, не вызывая повторно переданную функцию.</p>
24 <p>Первым параметром в хук передается функция, возвращаемое значение которой нужно запомнить, а вторым параметром - массив зависимостей. При изменении любой из указанных зависимостей будет вызываться переданная функция и вычисляться новый результат. С этой точки зрения useMemo похож на useEffect.</p>
24 <p>Первым параметром в хук передается функция, возвращаемое значение которой нужно запомнить, а вторым параметром - массив зависимостей. При изменении любой из указанных зависимостей будет вызываться переданная функция и вычисляться новый результат. С этой точки зрения useMemo похож на useEffect.</p>
25 <p>Доработаем наше приложение так, чтобы в качестве результата возвращалась функция:</p>
25 <p>Доработаем наше приложение так, чтобы в качестве результата возвращалась функция:</p>
26 <p>В useMemo передается функция () =&gt; () =&gt; setName('world'). Эта функция возвращает функцию () =&gt; setName('world'). В итоге эта возвращенная функция будет мемоизирована, и при отрисовке компонента App ссылка на функцию в переменной buttonHandler будет оставаться той же. Компонент Button перерисовываться не будет.</p>
26 <p>В useMemo передается функция () =&gt; () =&gt; setName('world'). Эта функция возвращает функцию () =&gt; setName('world'). В итоге эта возвращенная функция будет мемоизирована, и при отрисовке компонента App ссылка на функцию в переменной buttonHandler будет оставаться той же. Компонент Button перерисовываться не будет.</p>
27 <p>Обычно useMemo используют для каких-то сложных вычислений, чтобы не пересчитывать результат при одних и тех же параметрах. А для сохранения ссылки на функцию, как в нашем случае, используется похожий хук useCallback.</p>
27 <p>Обычно useMemo используют для каких-то сложных вычислений, чтобы не пересчитывать результат при одних и тех же параметрах. А для сохранения ссылки на функцию, как в нашем случае, используется похожий хук useCallback.</p>
28 <p>Важно знать, что useMemo() не гарантирует, что не будет нового вычисления. В какой-то момент React может вызвать мемоизированную функцию - обычно это происходит для освобождения новых ресурсов. Это значит, что вы не должны строить логику приложения полагаясь на useMemo(), эта функция предназначена лишь для оптимизации работы компонентов.</p>
28 <p>Важно знать, что useMemo() не гарантирует, что не будет нового вычисления. В какой-то момент React может вызвать мемоизированную функцию - обычно это происходит для освобождения новых ресурсов. Это значит, что вы не должны строить логику приложения полагаясь на useMemo(), эта функция предназначена лишь для оптимизации работы компонентов.</p>
29 <h2>useCallback</h2>
29 <h2>useCallback</h2>
30 <p>Хук useCallback() работает похожим образом как useMemo, только уже мемоизирует не результат вызова переданной функции, а саму функцию. Это позволяет немного сократить код и избавиться от лишних объявлений функций:</p>
30 <p>Хук useCallback() работает похожим образом как useMemo, только уже мемоизирует не результат вызова переданной функции, а саму функцию. Это позволяет немного сократить код и избавиться от лишних объявлений функций:</p>
31 <p>Как и useMemo, useCallback принимает вторым параметром массив зависимостей.</p>
31 <p>Как и useMemo, useCallback принимает вторым параметром массив зависимостей.</p>
32 <h2>Выводы</h2>
32 <h2>Выводы</h2>
33 <p>Мы изучили способы, как избежать лишних перерисовок компонентов. Знать эти инструменты важно, но бездумно использовать везде не стоит. Любая оптимизация тянет за собой усложнение кода.</p>
33 <p>Мы изучили способы, как избежать лишних перерисовок компонентов. Знать эти инструменты важно, но бездумно использовать везде не стоит. Любая оптимизация тянет за собой усложнение кода.</p>
34 <p>В этом уроке мы использовали простое приложение, но в обычной практике подобный проект не нуждается в такой оптимизации. Несколько раз подумайте, прежде чем что-то оптимизировать, изучите узкие места. Если приложение плохо работает, то чаще всего проблема находится в самой логике приложения. Поиск и исправление проблемных мест - отдельная тема, о которой мы поговорим в следующем уроке.</p>
34 <p>В этом уроке мы использовали простое приложение, но в обычной практике подобный проект не нуждается в такой оптимизации. Несколько раз подумайте, прежде чем что-то оптимизировать, изучите узкие места. Если приложение плохо работает, то чаще всего проблема находится в самой логике приложения. Поиск и исправление проблемных мест - отдельная тема, о которой мы поговорим в следующем уроке.</p>