Хуки useCallback и useMemo — JS: React Hooks
В разработке приложений на React часто возникают ситуации, когда компоненты отрисовываются без необходимости. Чаще всего это происходит при отрисовке родительского компонента или изменении пропсов.
Лишняя перерисовка компонентов может привести к непредвиденным событиям, когда при отрисовке компонента срабатывают побочные эффекты, либо перерисовка может повлиять на производительность.
Самый простой способ отследить перерисовку компонента — это расставить точки остановки выполнения кода в дебагере, либо добавить вывод лога.
Ниже простое приложение из двух компонентов:
import useState, useEffect > from 'react'; const Greeting = () => useEffect(() => console.log(`Компонент Greeting отрисован в $new Date().toLocaleTimeString()>`); >); return h3>Hello, world!h3>; >; const App = () => const [name, setName] = useState(''); return ( <> input value=name> onChange=(e) => setName(e.target.value)> /> Greeting /> > ); >;
В компонент Greeting мы добавили вывод в console.log() с меткой о времени для наглядности. Так мы сможем отследить время отрисовки компонента. Компонент App использует в представлении Greeting и содержит поле ввода, в котором меняется состояние компонента.
Если ввести значение в input , то в консоли браузера мы увидим вывод сообщения об отрисовке компонента Greeting . Компонент будет отрисовываться каждый раз, когда мы вводим значение в поле. Добавляем или удаляем один символ — и компонент перерисовывается. Если мы напечатаем слово hello , компонент перерисуется пять раз.
Расширение Profiler
Есть более удобный способ отслеживать отрисовку компонентов. Для этого нужно установить расширения для браузера React Developer Tools . Расширение добавляет несколько инструментов для работы с React-приложением. Один из них — это Profiler, позволяющий отслеживать отрисовку компонентов.
Нужно открыть вкладку Profiler в devtools, нажать кнопку записи слева и после этого взаимодействовать с приложением — например, ввести в поле любое значение, после этого остановить запись
Так будет выглядеть результат, если ввести в поле строку «hello» :
В правой верхней части можно переключаться между шагами рендера, Profiler сохраняет информацию о каждом рендере и отображает информацию сколько времени отрисовывался каждый компонент.
Пока может показаться, что это немного. Но в сложных приложениях с большим количеством компонентов такой инструмент может быть очень полезным.
Обычно при сборке React-приложение оптимизируется и поддержка профайлера из него убирается. Но вы можете использовать флаг —profile в команде сборки, если используете create-react-app , чтобы оставить поддержку инструмента на сервере:
-- --profile
Инструмент memo
В нашем приложении изменяется состояние корневого компонента. Это влечет за собой его перерисовку, что, в свою очередь, влечет за собой перерисовку всех дочерних компонентов. Компонент Greeting каждый раз перерисовывается. Это не всегда нужно, ведь в компоненте каждый раз рендерится одно и то же сообщение.
Такое поведение можно изменить, в React для этого используется инструмент мемоизации memo . Достаточно в него передать наш компонент:
import memo > from 'react'; const Memoized = memo(MyComponent);
Вот как будет выглядеть наше приложение:
import memo, useState, useEffect > from 'react'; const Greeting = memo(() => useEffect(() => console.log(`Компонент Greeting отрисован в $new Date().toLocaleTimeString()>`); >); return h3>Hello, world!h3>; >); const App = () => const [name, setName] = useState(''); return ( <> input value=name> onChange=(e) => setName(e.target.value)> /> Greeting /> > ); >;
Теперь, если ввести в поле значение, то по профайлеру или выводу логов мы увидим, что компонент Greeting не отрисовывается.
Это не значит, что компонент теперь всегда будет отрисовываться один раз. Если мы будем передавать в компонент измененный пропс, то перерисовка произойдет все равно:
import memo, useState, useEffect > from 'react'; const Greeting = memo(( name >) => useEffect(() => console.log(`Компонент Greeting отрисован в $new Date().toLocaleTimeString()>`); >); return h3>`Hello, $name>!`>h3>; >); const App = () => const [name, setName] = useState(''); return ( <> input value=name> onChange=(e) => setName(e.target.value)> /> Greeting name=name> /> > ); >;
Мемоизация и useMemo
Изменение пропсов может привести к ситуации, когда сами данные не меняются, но меняется ссылка на объект. В этом случае перерисовка произойдет, и memo не поможет. Немного модифицируем наше приложение для демонстрации такой ситуации:
import memo, useState > from 'react'; const Button = memo(( onClick >) => console.log(`Компонент Button отрисован в $new Date().toLocaleTimeString()>`); return button onClick=onClick>>Нажми меняbutton>; >); const Greeting = memo(( name >) => console.log(`Компонент Greeting отрисован в $new Date().toLocaleTimeString()>`); return h3>`Hello, $name>!`>h3>; >); const App = () => const [name, setName] = useState(''); const buttonHandler = () => setName('world'); return ( <> input value=name> onChange=(e) => setName(e.target.value)> /> Greeting name=name> /> Button onClick=buttonHandler> /> > ); >;
В приложение добавлен компонент Button , который принимает функцию для события onClick . Несмотря на то, что компонент мемоизирован, он все равно будет перерисовываться при каждой отрисовке App . Это происходит, потому что в компонент передается функция buttonHandler() . При каждой отрисовке App создается новая функция. React видит изменение ссылки на функцию и вызывает перерисовку компонента Button .
Чтобы избежать ненужных перерисовок компонента Button , нужно использовать хук useMemo . Он принимает функцию, которая возвращает какой-то результат. Хук запоминает этот результат и возвращает его каждый раз, не вызывая повторно переданную функцию.
Первым параметром в хук передается функция, возвращаемое значение которой нужно запомнить, а вторым параметром — массив зависимостей. При изменении любой из указанных зависимостей будет вызываться переданная функция и вычисляться новый результат. С этой точки зрения useMemo похож на useEffect .
Доработаем наше приложение так, чтобы в качестве результата возвращалась функция:
import memo, useState, useMemo > from 'react'; const Button = memo(( onClick >) => console.log(`Компонент Button отрисован в $new Date().toLocaleTimeString()>`); return button onClick=onClick>>Нажми меняbutton>; >); const Greeting = memo(( name >) => console.log(`Компонент Greeting отрисован в $new Date().toLocaleTimeString()>`); return h3>`Hello, $name>!`>h3>; >); const App = () => const [name, setName] = useState(''); const buttonHandler = useMemo(() => () => setName('world'), []); return ( <> input value=name> onChange=(e) => setName(e.target.value)> /> Greeting name=name> /> Button onClick=buttonHandler> /> > ); >;
В useMemo передается функция () => () => setName(‘world’) . Эта функция возвращает функцию () => setName(‘world’) . В итоге эта возвращенная функция будет мемоизирована, и при отрисовке компонента App ссылка на функцию в переменной buttonHandler будет оставаться той же. Компонент Button перерисовываться не будет.
Обычно useMemo используют для каких-то сложных вычислений, чтобы не пересчитывать результат при одних и тех же параметрах. А для сохранения ссылки на функцию, как в нашем случае, используется похожий хук useCallback .
Важно знать, что useMemo() не гарантирует, что не будет нового вычисления. В какой-то момент React может вызвать мемоизированную функцию — обычно это происходит для освобождения новых ресурсов. Это значит, что вы не должны строить логику приложения полагаясь на useMemo() , эта функция предназначена лишь для оптимизации работы компонентов.
useCallback
Хук useCallback() работает похожим образом как useMemo , только уже мемоизирует не результат вызова переданной функции, а саму функцию. Это позволяет немного сократить код и избавиться от лишних объявлений функций:
const buttonHandler = useCallback(() => setName('world'), []);
Как и useMemo , useCallback принимает вторым параметром массив зависимостей.
Как использовать React.memo и useCallback
В этой статье расскажу о том когда и как использовать React.memo и хук useCallback. После этой статьи поймете в каких случаях используют useCallback. Узнаете как работает между собой React.Memo и useCallback.
Кратко о React.memo и useCallback
React.Memo — компонент высшего порядка. Похож на React.PureComponent, но предназначен для функциональных компонентов.
Вторым аргументом React.Memo есть возможность передать функцию которая будет принимать решение ре-рендерить компонент на основе предыдущих и нынешних props
Компонент высшего порядка — функция принимает дочерний компонент и параметры, и затем создаёт контейнер поверх дочернего компонента.
React.PureComponent — схож с React.Component, но единственная разница в методе shouldComponentUpdate.
В PureComponent сравнивает новые значения с предыдущими и возвращает результат сравнения. React.Component — возвращает true
useCallback — хук вернёт мемоизированную функцию колбэка, который изменяется только, если изменяются значения из зависимостей.
Примеры использования
Base — не использует React.Memo. Компонент будет рендерится каждый раз
let count = 0
export default function Base(< action >) console.log(`$ <++count>Base component`)
return >Base
>
Memo — использует React.Memo. Компонент будет рендерится каждый раз когда меняются свойства
let count = 0
export default React.memo(function Memo(< action >) console.log(`$ <++count>Memo component`)
return >Memo
>)
NoUseCallback — не используем useCallback. Рендерится Base и Memo компоненты каждый раз
export default function NoUseCallback() const [a, setA] = useState('')
const [b, setB] = useState('')
useEffect(() => setTimeout(() => console.log('change value not related to action')
setA('a')
>, 1000)
setTimeout(() => console.log('change value related to action')
setB('b')
>, 3000)
>, [])
const action = () => console.log(b)
return (
<>
/>
/>
>
);
>
Console:
1 Base component
1 Memo component
change value not related to action
2 Base component
2 Memo component
change value related to action
3 Base component
3 Memo component
UseCallback — используем useCallback метод. Рендерится Base 3 и Memo 2 раза
export default function UseCallback() const [a, setA] = useState('')
const [b, setB] = useState('')
useEffect(() => setTimeout(() => console.log('change value not related to action')
setA('a')
>, 1000)
setTimeout(() => console.log('change value related to action')
setB('b')
>, 3000)
>, [])
const action = useCallback(() => console.log(b)
>, [b]);
return (
<>
/>
/>
>
);
>
Console:
1 Memo component
1 Base component
change value not related to action
2 Base component
change value related to action
2 Memo component
3 Base component
Заключение
Используем React.Memo когда хотим что бы компонент ререндерился когда меняются props или конкретные props.
Оборачиваем функцию в useCallback когда передаем другим компонентам или когда используются внешние переменные
React.js. Хук useCallback и зависимости
Смотрю сейчас на Udemy видео-курс по хукам — столкнулся с непониманием, зачем хуку useCallback нужны зависимости. Выяснил у преподавателя и решил записать, чтобы не наступать больше на эти грабли. Итак, есть два компонента — Parent и Child , в Parent есть функция, обернутая в useCallback , чтобы избежать лишних рендеров Child .
import React, useState, useCallback > from 'react'; import Child from './Child'; export default function Parent(props) const [state, setState] = useState('раз'); const handleParentClick = () => const newValue = Math.random() 0.5 ? 'раз' : 'два'; if (state !== newValue) console.log('Изменение состояния Parent'); setState(newValue); > >; const handleChildClick = useCallback(() => console.log('Обработчик клика Child'); >, []); console.log('Рендер компонента Parent'); return ( Родитель, состояние «state>» name="Ребенок" childClickHandler=handleChildClick> /> onClick=handleParentClick>>Попытка изменить состояние Parent ); >
import React from 'react'; const Child = React.memo((props) => console.log('Рендер компонента Child'); return onClick=props.childClickHandler>>props.name> >); export default Child;
Мы всегда передаем из Parent в Child через пропсы ссылку на одну и ту же функцию — благодаря хуку useCallback . По этой причине мы увидим рендер компонента Child только один раз — в момент монтирования. Когда происходит рендер Parent — это не будет вызывать рендер Child — так работает связка React.memo и useCallback .
Все работает хорошо, пока мы не обратимся внутри handleChildClick к переменной state — она почему-то всегда равна «раз».
const handleChildClick = useCallback(() => console.log('Обработчик клика Child'); console.log('Значение пременной state', state); >, []);
При первом рендере компонента Parent , в момент создания функции handleChildClick , она получает ссылку на лексическое окружение, в котором она создается. А хук useCallback сохранит ссылку на функцию handleChildClick , которая имеет замыкание над областью видимости функции Parent . Другими словами, при вызове функции handleChildClick ей будут доступны переменные state , setState , handleParentClick — какими они были в момент создания функции. Тут лучше прочитать у Ильи Кантора — как работает замыкание.
Поскольку useCallback с пустым массивом зависимостей будет всегда возвращать одну и ту же функцию, эта функция будет при вызове всегда выдавать одно и то же значение state . Это то самое значение «раз», какое было при первом рендере — и handleChildClick будет всегда доставать это значение из замыкания.
Чтобы console.log(state) показывал актуальное значение переменной state — нужно передать массив зависимостей при вызове хука.
const handleChildClick = useCallback(() => console.log('Обработчик клика Child'); console.log('Значение пременной state', state); >, [state]);
Теперь, когда state изменяет значение, функция будет создана заново, получит ссылку на текущее лексическое окружение, и при вызове будет иметь доступ к актуальному значению state через замыкание. Хук useCallback будет возвращать ссылку на эту новую функцию до тех пор, пока state снова не изменит свое значение.
При нажатии на кнопку «Попытка изменить состояние» — состояние Parent изменяется с вероятностью 50 процентов. Когда состояние изменяется — это вызывает рендер компонента Child , потому что ссылка, которую возвращает handleChildClick , изменилась. Когда состояние не изменяется — рендера Child не будет, потому что ссылка на функцию тоже остается без изменений.
- React.js. Почему функция-редюсер вызывается дважды
- React и Redux вместе. Часть 7 из 7
- React и Redux вместе. Часть 6 из 7
- React и Redux вместе. Часть 5 из 7
- React и Redux вместе. Часть 4 из 7
- React и Redux вместе. Часть 3 из 7
- React и Redux вместе. Часть 2 из 7
Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Категории блога
Облако тегов
- 1С:Предприятие (31)
- API (29)
- Bash (43)
- CLI (124)
- CMS (139)
- CSS (50)
- Frontend (75)
- HTML (66)
- JavaScript (150)
- Laravel (72)
- Linux (171)
- MySQL (76)
- PHP (125)
- React.js (66)
- SSH (27)
- Ubuntu (69)
- Web-разработка (509)
- WordPress (73)
- Yii2 (69)
- БазаДанных (95)
- Битрикс (66)
- Блог (29)
- Верстка (43)
- ИнтернетМагаз… (84)
- КаталогТоваров (87)
- Класс (30)
- Клиент (28)
- Ключ (28)
- Команда (88)
- Компонент (60)
- Конфигурация (66)
- Корзина (32)
- ЛокальнаяСеть (32)
- Модуль (34)
- Навигация (31)
- Настройка (143)
- ПанельУправле… (29)
- Плагин (33)
- Пользователь (26)
- Практика (101)
- Сервер (77)
- Событие (28)
- Теория (106)
- Установка (67)
- Файл (51)
- Форма (58)
- Фреймворк (192)
- Функция (36)
- ШаблонСайта (68)
Usecallback hook react зачем

Другими словами, когда компонент собирается повторно визуализироваться, React сравнивает каждый созданный под его исходным компонентом объект с новой версией этого объекта. И хотя объекты точно такие же, они не указывают на один и тот же объект. React идентифицирует их как разные объекты, позволяя воссоздавать снова и снова при каждом повторном рендеринге.
Для чего нужны useCallback и useMemo?
Если useCallback и useMemo используются правильно, они необходимы, чтобы предотвратить повторные рендеры и сделать код более эффективным.
Рассмотрим их структуру. Оба хука получают два параметра: функцию и массив зависимостей.

useCallback возвращает один и тот же экземпляр передаваемой функции (параметр 1) вместо создания нового при каждом повторном рендеринге компонента. Новый экземпляр передаваемой функции (параметр 1) может быть создан только при изменении массива зависимостей (параметр 2).
Рассмотрим пример: приложение, которое увеличивает первое значение и/или уменьшает второе, а также обновляет итоговый результат. Есть еще третье дополнительное значение, которое пользователь может обновить, однако это число не используется в вычислениях.

useMemo используется вместо того, чтобы возвращать невызванную функцию, как это делает useCallback – он работает с передаваемой функцией и возвращает результирующее значение только при изменении массива параметров. Другими словами, useMemo вызывает функцию только при необходимости и возвращает кэшированное значение для других визуализаций.
В этом примере мы реализовали приложение, которое принимает число и возвращает его факториал. Например, если передать число 5, программа использовала бы рекурсивную функцию для вычисления: 5! = 5*4*3*2*1 = 120. Здесь мы использовали хук useMemo, чтобы React занимался пересчетом только при изменении числа.

Обновление значения ref считается побочным эффектом. Именно по этой причине необходимо обновить значение ref в обработчиках событий и эффектах, а не во время визуализации (если только вы не работаете с ленивой инициализацией). React docs предупреждает нас, что несоблюдение этого правила может привести к неожиданному поведению приложения.
Использовать ли refs вместо state?
Нет. Refs – нереактивный, а значит, изменения значений не приведет к обновлению HTML. Взглянем на следующий пример. Мы инициализировали state, ref и 1000$. Этот компонент позволяет вам тратить эту сумму доллар за долларом каждый раз, когда нажимается кнопка «Spend».
Когда происходит трата хранящихся в состоянии средств, запускается повторный рендеринг и обновляется view, чтобы показать вам новое значение.
Теперь, если вы потратите хранящиеся в ref средства, они будут вычитаться за каждый клик, но это изменение не вызовет повторного рендеринга – вы не увидите изменений на странице.

useRef() аналог createRef?
Нет, createRef() полезен для доступа к узлам DOM или элементам React, но он создает новый экземпляр ref на каждом рендере вместо того, чтобы сохранять значение между визуализациями при использовании в функциональных компонентах.
useRef() полезен для доступа к узлам DOM или элементам React. Он сохраняет значение даже при повторной визуализации компонента. Вот пример, который позволит увидеть разницу. Мы инициализируем два значения, используя createRef и useRef. Каждый раз, когда нажимается кнопка «Add a render!», обновляется состояние renderCounter и вызывается повторная визуализация, в процессе которой проверяется, являются ли значения refs нулевыми. Если да, то присваиваем ему текущее значение состояния renderCounter.
Обратите внимание, что созданное с помощью useRef значение ref равно null только при первом рендеринге, поэтому оно устанавливается в 1 единственный раз.
С другой стороны, созданное с помощью createRef значение ref создается при каждом рендеринге, поэтому оно всегда null, а затем приравнивается к текущему значению состояния renderCounter.

React предоставляет нам поток данных, в котором родительский компонент использует props для обмена информацией со своими дочерними компонентами. Этот способ отслеживания данных отлично подходит для небольших приложений, однако по мере роста проекта вы можете обнаружить, что происходит проброс props через несколько слоев компонентов. Это называется prop drilling.
При таком эффекте с использованием нескольких слоев задача обслуживания может стать очень сложной и громоздкой. Кроме того, рефакторинг кода приведет к передаче ненужных props или к использованию нескольких имен для одного props (появление бага).
Альтернативой prop drilling является использование Context – простого решения, которое дает возможность доступа к данным между компонентами, даже если они не имеют отношений родитель-потомок.
Что такое context object?
C ontext object создается с помощью API createContext() и состоит из двух элементов: провайдер и потребитель. Чтобы создать объект контекста, вы можете инициализировать его пустым или со значением:

Reducer – это событие, которое будет выполнено, чтобы получить только одно значение. Возвращаемое значение может быть числом, строкой, массивом или даже объектом, если это оно единственное. Кроме того, reducer возвращают новое значение, а не мутируют начальное.
Они очень полезны, когда необходимо получить одно значение после применения некоторой логики к группе значений. Например, если вы хотите сложить массив чисел для получения общего значения.
Мы применяем метод reduce к массиву чисел nums = [1,2,3,4,5] . Метод принимает два параметра:
reducer – функция, предоставляющая инструкции для получения одного значения. В нашем случае, для суммирования всех заданных значений в массиве nums.
const reducer = (accumulator, currentValue) => accumulator + currentValue;
initialValue – начальное значение при реализации инструкций функции reducer. В нашем примере мы определяем начальное значение как 0, поэтому общее возвращаемое значение отражает только сумму значений в массиве nums.
const initialValue = 0;
Теперь посмотрим на все это вместе. Метод reduce принимает initialValue и строит его, следуя приведенным в функции reducer инструкциям и добавляя каждое значение в массив nums до тех пор, пока не сможет вернуть одно общее значение.
const reducer = (accumulator, currentValue) => accumulator + currentValue; const nums = [1,2,3,4,5]; const initialValue = 0; const totalValue = nums.reduce(reducer, initialValue);
Что такое useReducer()?
Хук useReducer используется для управления состоянием. В нем есть следующие параметры:
reducer – предоставляющую инструкции по управлению состоянием функция, которая принимает параметры state и action и возвращает новое состояние.
(state, action) => newState
initialState – значение начальной точки. Оно будет меняться в соответствии с инструкциями reducer.
Похоже на описанное ранее поведение функции reduce, но хук useReducer не возвращает только одно значение. Вместо этого он возвращает два элемента в виде массива: текущее состояние и функцию отправки.
const [state, dispatch] = useReducer(reducer, initialState);
Когда следует использовать useReducer?
useReducer является предпочтительной альтернативой useState в следующих случаях:
- при работе со сложной логикой состояний, включающей несколько значений;
- при работе со значениями состояния, зависящими от состояния других элементов состояния.
Резюме
Метод reduce полезен для получения одного значения после применения некоторой логики к группе значений.
- редьюсеры возвращают новое значение вместо того, чтобы мутировать начальное;
- useReducer используется с управлением состоянием;
- useReducer следует использовать при работе со сложной логикой состояний, несколькими подзначениями или когда состояние зависит от подзначений состояний.
Заключение
Мы рассмотрели очень обширную и интересную тему, требующую большой практики и дополнительного изучения. Рекомендуем ознакомиться с официальной документацией и продолжать обучение. Удачи!
- Локализация приложений React с i18next
- Настольный справочник по базовым принципам React
- 12 бесплатных ресурсов для изучения React
- Каркасные экраны: реализация в React
- Изучение React. С чего начать?