[JavaScript, ReactJS] Все ли вы знаете о useCallback
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Привет, Хабр!Начиная с версии ReactJS 16.8 в наш обиход вошли хуки. Этот функционал вызвал много споров, и на это есть свои причины. В данной статье мы рассмотрим одно из самых популярных заблуждений использования хуков и заодно разберемся стоит ли писать компоненты на классах (данная статья является расшифровкой видео).Два путиКак вы знаете в реакте есть 2 вариант написания компонента, с помощью классов и с помощью функций. И каждый вариант по своему взаимодействует с методами. Давайте рассмотрим оба варианта:Метод в классеПервый вариант, это использовать классы:
class Test extends Component {
onClick = () => {
console.log('onClick');
}
render() {
return (
<button onClick={this.onClick}>
test
</button>
)
}
}
В данном варианте мы добавили метод onClick классу Test и при создании инстанса класса, этот метод создается 1 раз и в рендере мы уже используем ссылку на этот метод onClick={this.onClick}, таким образом при каждом рендере мы обращаемся всегда к одной и той же ссылке и не пересоздаем метод класса. Эта конструкция всем, кто давно в профессии, привычна и понятна даже если вы недавно пришли в React с другого языка программирования.Метод в функцииВторой способ создания компонента является использование функции:
const Test = () => {
const onClick = () => {
console.log('onClick');
}
return (
<button onClick={onClick}>
test
</button>
)
}
В таком подходе, чтобы создать обработчик onClick, мы описываем тело функции прямо внутри render, потому что все тело функции и есть render, другого варианта в принципе не существует, если вы хотите использовать props.И тут у нас начинает зудеть в боку, да как это так, мы же теперь заново создаем функцию, абсолютно на каждый рендер. По сравнению с классами это как будто шаг назад.Классы лучше чем функции?Чтобы разобраться с этим вопросом я полез в React документацию в секцию вопросы и ответы и нашел там следующий вопрос:
Судя по документации создание инстанса класса для реакта настолько дорогостоящая операция, что создавать функцию на каждый рендер на порядок дешевле. Да и тот факт, что дерево становится глубже при использовании компонента высшего порядка connect от redux или бесконечных observer от mobX совсем не радует.Кажется есть один "вариантик" сэкономитьИдею, создавать на каждый рендер новую функцию и думать что это дешевле, чем один раз создать инстанс класса, немного сложно принять разработчикам, потому что с нашей стороны мы должны писать код хуже, и верить что приложение ускорится. Звучит крайне противоречиво, а мы привыкли все оптимизировать.Как результат, мы начинаем искать пути, как выйти на прежний уровень оптимизации с нашей стороны. И первое что гуглится, это начать использовать хук useCallback. И многие особо не вникая в суть происходящего начинают его активно использовать. Чтобы разобраться во всем этом давайте устроим небольшую викторинуВикторина!Сейчас мы рассмотрим 2 примера и Вы попытаетесь ответить кто круче!В одном углу ринга находится уже изученный нами ранее вариант написания обработчика события someFunction:
const Test = ({ title }) => {
const someFunction = () => {
console.log(title);
}
return (
<button onClick={someFunction}>
click me!
</button>
)
}
В другом углу ринга находится точно такой же компонент, но уже решили завернуть функцию в useCallback.
const Test = () => {
const someFunction = useCallback((title) => {
console.log(title);
}, [title])
return (
<button onClick={someFunction}>
click me!
</button>
)
}
Для пользователя ничего не изменилось, console.log(title), точно так же вызывается при нажатии на кнопку.Внимание вопросВ каком из вариантов написания компонента функция присваемая переменной someFunction создается реже?
Даем минутку подумать... Аккуратно ответ! ОтветИ правильный ответ ни в каком! Да именно, никакой оптимизации useCallback нам не дал, функция создается ровно столько же раз, как и до оптимизации. Более того, мы наоборот ухудшили перфоманс нашего компонента.Разбираем ответТо что многих вводит в заблуждение - это представление что useCallback, это какой-то черный ящик, в который ты отдаешь функцию, с ней что-то происходит и после будет тебе счастье. Но давайте рассмотри как это работает на самом деле. Для начала сделаем typeof этого черного ящика:
Естественно мы получим результат function. По синтаксису это было очевидно. Чтобы понять как работает черный ящик, давайте сами напишем имплементацию useCallback.Пишем свой useCallbackuseCallback - это функция, которая принимает 2 параметра, callback и deps.
function useCallback (callback, deps) {
}
Далее нам надо хранить где-то этот callback и deps, чтобы иметь возможность при очередном вызове вернуть ту же самую функцию callback.
const prevState = {
callback: null,
deps: null,
}
function useCallback(callback, deps) {
}
Теперь рассмотрим разные случаи: если deps не существует либо в prevState, либо в новых данных, тогда нужно сохранить текущие параметры и вернуть callback.
const prevState = {
callback: null,
deps: null,
}
function useCallback(callback, deps) {
if (!prevState.deps || !deps) {
prevState.callback = callback;
prevState.deps = deps;
return callback;
}
}
Если же deps существуют. Тогда сравниваем какой-либо функцией массивы и если они совпадают, тогда возвращаем мемоизированную функцию.
const prevState = {
callback: null,
deps: null,
}
function useCallback(callback, deps) {
if (!prevState.deps || !deps) {
prevState.callback = callback;
prevState.deps = deps;
return callback;
}
if (shallowEqual(deps, prevState.deps)) {
return prevState.callback;
}
}
Ну и если deps не совпадают, тогда снова сохраняем параметры и возвращаем текущий callback.
const prevState = {
callback: null,
deps: null,
}
function useCallback(callback, deps) {
if (!prevState.deps || !deps) {
prevState.callback = callback;
prevState.deps = deps;
return callback;
}
if (shallowEqual(deps, prevState.deps)) {
return prevState.callback;
}
prevState.callback = callback;
prevState.deps = deps;
return callback;
}
Вроде бы мы покрыли все кейсыКакие выводы из этого мы можем сделать?Функция useCallback как и любая другая функция вызывается на каждый рендер и в качестве параметра callback каждый рендер приходит новая функция и новый массив зависимостей. Которые мы либо выбрасываем, если зависимости до и после совпадают, либо сохраняем в хранилище, для будущего использования.Давайте теперь посмотрим на эту функцию со стороны компонента. Мы знаем, что useCallback это просто функция и мы можем извлечь передаваемые параметры в отдельные переменные.
const Test = ({ title }) => {
const callback = () => {
console.log(title);
}
const deps = [title];
const someFunction = useCallback(callback, deps);
return (
<button onClick={someFunction}>
click me!
</button>
)
}
Тут становится совсем очевидно, что мы на каждый рендер создаем не то что функцию, а еще и массив с зависимостями, а потом еще и прокручиваем все это через useCallback.Если мы просто закомментируем создание зависимостей и вызов useCallback и передадим параметр callback напрямую в onClick, тогда кажется перфоманс компонента должен улучшиться, ведь мы убрали посредника, который не нес никакой пользы для перфоманса.
const Test = ({ title }) => {
const callback = () => {
console.log(title);
}
// const deps = [title];
// const someFunction = useCallback(callback, deps);
return (
<button onClick={callback}>
click me!
</button>
)
}
По итогу мы вернулись к начальной ситуации. Когда просто создавали функцию на каждый рендер. Получается, в данном случае использовать хук useCallback - это не значит улучшить перфоманс, а скорее совсем наоборот, ухудшить перфоманс.
А для чего тогда нужен useCallback ?Получается мы как то не так используем useCallback. Чтобы разобраться в этом, давайте обратимся к документации:
Получается основная идея не в улучшении перформанса в конкретном компоненте, а скорее использование useCallback выгодно только в случае передачи функции как props. Давайте рассмотрим еще один пример.Допустим у нас есть список машин, который мы хотим отобразить:
const Cars = ({ cars }) => {
return cars.map((car) => {
return (
<Car key={car.id} car={car} />
)
});
}
Тут нам понадобилось добавить обработчик клика на машину. Мы создаем метод onCarClick и передаем его в компонент Car.
const Cars = ({ cars }) => {
const onCarClick = (car) => {
console.log(car.model);
}
return cars.map((car) => {
return (
<Car key={car.id} car={car} onCarClick={onCarClick} />
)
});
}
В такой ситуации на каждый рендер компонента Cars у нас создается новая функция onCarClick, соответственно, не важно Car это PureComponent или обернут в memo, все машины всегда будут заново рендерится, т.к. получают каждый раз новую ссылку на функцию.Для этого и нужен useCallback, если мы завернем функцию в хук, то у нас в переменной onCarClick будет уже возвращаться мемоизированая функция, хоть мы в нее на каждый рендер и передаем новую функцию
const Cars = ({ cars }) => {
const onCarClick = useCallback((car) => {
console.log(car.model);
}, []);
return cars.map((car) => {
return (
<Car key={car.id} car={car} onCarClick={onCarClick} />
)
});
}
Таким образом все компоненты Car не будут рендериться лишний раз, т.к. ссылка на функцию останется прежней. А если заглянуть внутрь компонента Car. Там мы создадим еще одну функцию, которая свяжет onCarClick и объект car.
const Car = ({ car, onCarClick }) => {
const onClick = () => onCarClick(car);
return (
<button onClick={onClick}>{car.model}</button>
)
}
В этом случае нет никакой пользы оборачивать метод в useCallback, т.к. нам не важно, ссылка это на функцию с прошлого рендера или с текущего рендера, а useCallback как мы уже знаем не бесплатный.ИтогиПодытожить данную статью можно следующими словами. React хоть и поддерживает компоненты в виде классов, но имеет больше маневров над ускорением именно компонентов в виде функций. Да и сама экосистема все больше в качестве API предоставляет вам именно хуки, что невозможно использовать в классах:
import { useLocation } from "react-router-dom";
import { useSelector } from "react-redux";
import { useLocalObservable } from "mobx-react-lite";
import { useTranslation } from "react-i18next";
И конечно, доверяйте реакту, если они сказали лучше создавать функцию на каждый рендер, так и делайте, ведь они заинтересованы только в улучшении перформанса вашего проекта.А если вам понравилось данная статья, то здесь есть еще немного интересногоЧао
===========
Источник:
habr.com
===========
Похожие новости:
- [Разработка веб-сайтов, ReactJS, Повышение конверсии, Поисковая оптимизация] SEO-оптимизация сайта на React или как добиться конверсии от поисковиков если у вас Single Page Application
- [Open source, JavaScript, HTML, Canvas, Инфографика] Опыт использования транслятора OberonJS для создания редактора интерактивных моделей
- [JavaScript, HTML, Canvas, Визуализация данных, Дизайн игр] Индикатор искусственного горизонта на HTML5 canvas
- [JavaScript, Программирование, Node.JS, Rust] Обнаружение лиц в Node.js с использованием Rust и WebAssembly (перевод)
- [JavaScript, Программирование, Учебный процесс в IT, Карьера в IT-индустрии] Веб-тренажёр Яндекс.Практикума. Как всё устроено
- [Программирование] Вы не знаете деструктуризацию, пока (перевод)
- [JavaScript, Программирование] Функции в JavaScript: секреты, о которых вы не слышали (перевод)
- [JavaScript, Веб-аналитика, Интернет-маркетинг, Повышение конверсии, Поисковая оптимизация] Используем Google Tag Manager Server-Side вместо Zapier
- [Разработка веб-сайтов, JavaScript, Программирование] Введение в реактивное программирование
- [Разработка веб-сайтов, JavaScript, Программирование] Детальный обзор Well-known Symbols (перевод)
Теги для поиска: #_javascript, #_reactjs, #_reactjs, #_javascript, #_react_hooks, #_usecallback, #_react, #_javascript, #_reactjs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:42
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Привет, Хабр!Начиная с версии ReactJS 16.8 в наш обиход вошли хуки. Этот функционал вызвал много споров, и на это есть свои причины. В данной статье мы рассмотрим одно из самых популярных заблуждений использования хуков и заодно разберемся стоит ли писать компоненты на классах (данная статья является расшифровкой видео).Два путиКак вы знаете в реакте есть 2 вариант написания компонента, с помощью классов и с помощью функций. И каждый вариант по своему взаимодействует с методами. Давайте рассмотрим оба варианта:Метод в классеПервый вариант, это использовать классы: class Test extends Component {
onClick = () => { console.log('onClick'); } render() { return ( <button onClick={this.onClick}> test </button> ) } } const Test = () => {
const onClick = () => { console.log('onClick'); } return ( <button onClick={onClick}> test </button> ) } Судя по документации создание инстанса класса для реакта настолько дорогостоящая операция, что создавать функцию на каждый рендер на порядок дешевле. Да и тот факт, что дерево становится глубже при использовании компонента высшего порядка connect от redux или бесконечных observer от mobX совсем не радует.Кажется есть один "вариантик" сэкономитьИдею, создавать на каждый рендер новую функцию и думать что это дешевле, чем один раз создать инстанс класса, немного сложно принять разработчикам, потому что с нашей стороны мы должны писать код хуже, и верить что приложение ускорится. Звучит крайне противоречиво, а мы привыкли все оптимизировать.Как результат, мы начинаем искать пути, как выйти на прежний уровень оптимизации с нашей стороны. И первое что гуглится, это начать использовать хук useCallback. И многие особо не вникая в суть происходящего начинают его активно использовать. Чтобы разобраться во всем этом давайте устроим небольшую викторинуВикторина!Сейчас мы рассмотрим 2 примера и Вы попытаетесь ответить кто круче!В одном углу ринга находится уже изученный нами ранее вариант написания обработчика события someFunction: const Test = ({ title }) => {
const someFunction = () => { console.log(title); } return ( <button onClick={someFunction}> click me! </button> ) } const Test = () => {
const someFunction = useCallback((title) => { console.log(title); }, [title]) return ( <button onClick={someFunction}> click me! </button> ) } Даем минутку подумать... Аккуратно ответ! ОтветИ правильный ответ ни в каком! Да именно, никакой оптимизации useCallback нам не дал, функция создается ровно столько же раз, как и до оптимизации. Более того, мы наоборот ухудшили перфоманс нашего компонента.Разбираем ответТо что многих вводит в заблуждение - это представление что useCallback, это какой-то черный ящик, в который ты отдаешь функцию, с ней что-то происходит и после будет тебе счастье. Но давайте рассмотри как это работает на самом деле. Для начала сделаем typeof этого черного ящика: Естественно мы получим результат function. По синтаксису это было очевидно. Чтобы понять как работает черный ящик, давайте сами напишем имплементацию useCallback.Пишем свой useCallbackuseCallback - это функция, которая принимает 2 параметра, callback и deps. function useCallback (callback, deps) {
} const prevState = {
callback: null, deps: null, } function useCallback(callback, deps) { } const prevState = {
callback: null, deps: null, } function useCallback(callback, deps) { if (!prevState.deps || !deps) { prevState.callback = callback; prevState.deps = deps; return callback; } } const prevState = {
callback: null, deps: null, } function useCallback(callback, deps) { if (!prevState.deps || !deps) { prevState.callback = callback; prevState.deps = deps; return callback; } if (shallowEqual(deps, prevState.deps)) { return prevState.callback; } } const prevState = {
callback: null, deps: null, } function useCallback(callback, deps) { if (!prevState.deps || !deps) { prevState.callback = callback; prevState.deps = deps; return callback; } if (shallowEqual(deps, prevState.deps)) { return prevState.callback; } prevState.callback = callback; prevState.deps = deps; return callback; } const Test = ({ title }) => {
const callback = () => { console.log(title); } const deps = [title]; const someFunction = useCallback(callback, deps); return ( <button onClick={someFunction}> click me! </button> ) } const Test = ({ title }) => {
const callback = () => { console.log(title); } // const deps = [title]; // const someFunction = useCallback(callback, deps); return ( <button onClick={callback}> click me! </button> ) } А для чего тогда нужен useCallback ?Получается мы как то не так используем useCallback. Чтобы разобраться в этом, давайте обратимся к документации: Получается основная идея не в улучшении перформанса в конкретном компоненте, а скорее использование useCallback выгодно только в случае передачи функции как props. Давайте рассмотрим еще один пример.Допустим у нас есть список машин, который мы хотим отобразить: const Cars = ({ cars }) => {
return cars.map((car) => { return ( <Car key={car.id} car={car} /> ) }); } const Cars = ({ cars }) => {
const onCarClick = (car) => { console.log(car.model); } return cars.map((car) => { return ( <Car key={car.id} car={car} onCarClick={onCarClick} /> ) }); } const Cars = ({ cars }) => {
const onCarClick = useCallback((car) => { console.log(car.model); }, []); return cars.map((car) => { return ( <Car key={car.id} car={car} onCarClick={onCarClick} /> ) }); } const Car = ({ car, onCarClick }) => {
const onClick = () => onCarClick(car); return ( <button onClick={onClick}>{car.model}</button> ) } import { useLocation } from "react-router-dom";
import { useSelector } from "react-redux"; import { useLocalObservable } from "mobx-react-lite"; import { useTranslation } from "react-i18next"; =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:42
Часовой пояс: UTC + 5