3:39 В случае, если функцию debounce использовать просто внутри компонента, то она будет отрабатывать неадекватно. При вызове onChange больше одного раза начнется мемори лик, т.к фунция updateSuggestions будет всегда новая, соответсвенно и таймеры в ней будут новые и никакой связи с предыдущем рендером не будет. Поэтому у каждого debounce должна быть функция cancel для ручной остановки таймера. Чтобы всего этого избежать, как вариант, можно вынести функцию debounce за компонент, как показано в видео, или же обернуть ее в useMemo. Что-то не верится, что с подобным ты не сталкивался)
спасибо за подробное описание проблемы! по хорошему конечно debounce функцию надо было еще в useCallback внутри компонента обернуть) но почему то опустил этот момент)
@@it-sin9k debounce лучше в useMemo оборачивать Хоть useCallback и запомнит ту функцию, что debounce вернёт при первом вызове, но после каждого ре-рендера debounce будет возвращать новую функцию, которая будет улетать в никуда
Выносить функцию за компонент как это предложил Синяк, это тоже костыль. В таком случае у нас один инстанс задебаунсенной функции с таймером на все инстансы компонента, а их может быть хоть сколько, что в целом нарушает изолированную работу отдельных инстансов друг от друга. Прям жутких багов в данном примере от этого не будет, но в каких-то других кейсах может крайне незаметно стрельнуть. Я считаю надо либо использовать useDebouncedCallback, либо использовать комбинацию debounce с useMemo. А ещё при анмаунте компонента надо отменять таймер дебаунса, поэтому все равно лучше заворачивать debounce в хук, чтобы всё сделать аккуратно
@@it-sin9k я думаю стоит рассказать у нас таким большая часть кода была обмазана и в итоге оно начало сильно стрелять, и я в этот момент как раз пришел в компанию) там даже не понимали такие проблемы и не знали как решить, тут то мне мои боли и знания пригодились))
Не игнорируйте экономию на спичках. На моем текущем проекте их сейчас столько рассыпано, что он только из них и состоит. Потихоньку разгребаю, уже ввёл несколько паттернов чтобы не плодить новые спички.
Это не отменяет того, что при анмаунте компонента таймер чиститься не будет а если живёт таймер - то сохраняется ссылка на коллбэк, соответственно и сама функция со своим контекстом тоже жива) как минимум, вывод - утечка памяти)
Отличный разбор! Основное преимущество ваших разборов - это здравый смысл, объективность и желание разобраться со всеми реальными деталями вопроса. Огромное спасибо за качественный инженерный контент!!!
Привет. Подскажи пожалуйста, а на сколько актуально и безопасно прокидывать сеттер из useState параметром в функцию? Как это сделал ты, чтобы вынести функцию за пределы компонента. Я просто как-то задумывался над таким паттерном. Это ведь хорошая возможность оставить в компоненте только одну разметку и стейты, вынеся все функции куда-нибудь рядом в utils. У меня на работе был похожий кейс. Мы используем Redux и Redux-saga. И при диспатче экшена я прокинул сеттер закрытия модалки в сагу. После успешного окончания запроса я вызвал сеттер, чтобы закрыть модалку. Мне на ревью сказали, что так делать не следует. Пришлось создавать в редьюсере свойство, отвечающее за состояние открытия модалки. Что на мой взгляд выглядит более костыльно, а при усложнении логики, поддерживать это становится сложнее.
Так как я сделал со стейт функцией лучше особо не использовать. Мне стоило быть аккуратнее и вернуть результат обратно и там уже использовать стейт функцию. Основная проблема в том, что уже не будут многие знать, что эта функция принадлежит какому-то стейту. А может быть вообще ситуация, что компонент размаунчен, а кто то стейт обновляет. Что тоже есть проблема Но и в redux хранить состояние модального окна я тоже считаю так себе вариантом :) Я планирую перезапустить пару видео про модльные окна и там поделюсь вариантами, как бы я делал)
@@it-sin9k я еще под первым видео пытался придумать адекватный вариант решения проблемы с состоянием гонки и ничего толкового не придумал, о чем там же и написал. Вы, кстати, писали, что собираетесь записать видео по этому поводу, насколько я понял. Прикрутить debounce к коду без useEffect не то чтобы сложно, в отличии от решения проблемы с race condition. По крайней мере для меня.
я делаю не 230 и даже не в 93 строки кода, а в 5 используя useEffect (так как мне нужно как правило работать с api). Просто запускаю setTimeout, если таймер не истек, обнуляю счетчик.
Если lodash уже установлен в проект, тогда да. Использую debounce из lodash. Если же в проекте нет lodash, в таком случае использую один из способов: пишу свой простой deboucne, либо использую отдельный npm пакет debounce
Спасибо за видео. Но я все еще не понял как отменять в таком подходе предыдущее асинхронное действие. Разве что как то с abort controller делать. Как пример, это просто и понятно работает в сагах с takeLatest Цель этого видео не про это, но все же
В даном примере можно не использовать дебаунс, достаточно использовать аборт контролер и передать его в фетч запрос. Таким образом, слишком частые запросы будут канселиться😊
Это здорово что вы такое показываете. Но интересно другое. Сначала мы такие давайте пользоваться реактом и его фишками. А потом не, давайте на обычном js функции с сет таймаутом писать. Конечно в основе лежит js, но хотелось бы пользоваться только реактом как целостным решением, а не городить свои, пусть даже более эффективные.
АйтиСиняк, приветствую Не могу найти старый видос об модальных окнах, где вы рассказывали, как вы их делаете в вашей компании или что-то такое. Там описывались все действия от начала и до конца, мол нажимаем на кнопку, записываем это в URL и т.д. Он удален?
@@it-sin9k Пример использования сам по себе чуть ли не анти-паттерн, если уж делать серию видосов про замену useEffect на что-то ещё, то и примеры хотелось бы видеть с актуальным использованием useEffect. Да и видос больше про дебаунс сам по себе, нежели useEffect, и раз уж о нём речь зашла, то неплохо было бы тут эту дебаунс обёртку в компоненте в useEvent обернуть, но это так, к слову
@@it-sin9k Пример всё тот же, по сути чуть ли не "антипаттерн" применения useEffect, всё же хотелось увидеть его реальное применение и альтернативы к нему, а видео вообще больше про дебаунс. Который неплохо было бы обернуть в useEvent, если уж в компоненте этот дебанус использовать, преимущественно начинающие ведь смотрят, как никак (и тут уже, возможно, напрашивается useDebounce хук, который будет внутри эту обёртку делать).
Мне кажется видосы надо снимать изначально более продумано и рассматривать проблему с разных сторон, чтобы донести свою позицию и найти в ней изъяны, если таковые имеются. Вторая часть намного лучше первой.
@@it-sin9k в этом случае раздельно получается огрызок. Первое видео как будто из пальца высосано. Не информативно, зачем не понятно, почему так не ясно. В общем много вопросов вызывает.
1. По-хорошему useEffect все равно нужен. Он необходим для очистки debounce таймера при анмаунте компонента. 2. Пример с выносом debounce функции из компонента работает только если она используется единственным экземпляром этого компонента. Если же ее вызывают несколько экземпляров, например инпутов в одной форме, то вызов той же функции на другом инпуте прервет последний таймер, вызванный предыдущим инпутом. Спасибо за видео!
1. рассуждение теоретически полностью верные. Но на практике у debounce задержка 300 ms и это совсем редкий кейс, когда за это время может произойти unmount во время того, как пользователь пользуется инпутом из этого компонента. Поэтому я обычно игнорирую 2. опять же, если речь идет про инпут, не может человек печатать сразу в двух инпутах. Поэтому проблемы особой быть не должно. Да и случай пересечения, если пользователь умудрится это сделать не несет никакого урона. Поэтому получается разделение такого кейса, никто не оценит, а код станет сложнее)
Согласен, что кейс редкий и такой подход помимо ворнингов в консоли в большинстве случаев никак себя не проявит, особенно в контексте рассмотренных примеров в видео. Однако, в тех редких случаях когда это стреляет - может быть крайне не приятно.
кто бы мог подумать, что простое видео про debounce станет убийцей пакета useDebounce! акции пакета рухнули))
ахахах) не думаю, что у меня такое большое влияние)
Как насчёт такого примера?
function useDebounce(value: T, delay?: number): T {
const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay ?? 500)
return () => {
clearTimeout(timer)
}
}, [value, delay])
return debouncedValue
}
можно и так) не хватает разве что ручек как почистить почистить программатикали
или так
export function useDebounce(callback: () => void, deps: DependencyList, delay: number) {
useEffect(() => {
const timer = setTimeout(() => {
callback()
}, delay)
return () => clearTimeout(timer)
}, deps)
}
используем
useDebounce(() => {
}, зависимости)
export function useDebounce(callback: () => void, deps: DependencyList, delay: number) {
useEffect(() => {
const timer = setTimeout(callback, delay)
return () => clearTimeout(timer)
}, deps)
})
3:39 В случае, если функцию debounce использовать просто внутри компонента, то она будет отрабатывать неадекватно. При вызове onChange больше одного раза начнется мемори лик, т.к фунция updateSuggestions будет всегда новая, соответсвенно и таймеры в ней будут новые и никакой связи с предыдущем рендером не будет. Поэтому у каждого debounce должна быть функция cancel для ручной остановки таймера. Чтобы всего этого избежать, как вариант, можно вынести функцию debounce за компонент, как показано в видео, или же обернуть ее в useMemo. Что-то не верится, что с подобным ты не сталкивался)
спасибо за подробное описание проблемы! по хорошему конечно debounce функцию надо было еще в useCallback внутри компонента обернуть) но почему то опустил этот момент)
@@it-sin9k debounce лучше в useMemo оборачивать
Хоть useCallback и запомнит ту функцию, что debounce вернёт при первом вызове, но после каждого ре-рендера debounce будет возвращать новую функцию, которая будет улетать в никуда
Выносить функцию за компонент как это предложил Синяк, это тоже костыль. В таком случае у нас один инстанс задебаунсенной функции с таймером на все инстансы компонента, а их может быть хоть сколько, что в целом нарушает изолированную работу отдельных инстансов друг от друга. Прям жутких багов в данном примере от этого не будет, но в каких-то других кейсах может крайне незаметно стрельнуть. Я считаю надо либо использовать useDebouncedCallback, либо использовать комбинацию debounce с useMemo. А ещё при анмаунте компонента надо отменять таймер дебаунса, поэтому все равно лучше заворачивать debounce в хук, чтобы всё сделать аккуратно
а теперь берем и юзаем компонент в 2х местах
берем и отмонтируем компонент, а дебаунс висит и шлет запрос :)
про race condition и unmount видимо должна быть отдельная история)
@@it-sin9k я думаю стоит рассказать
у нас таким большая часть кода была обмазана и в итоге оно начало сильно стрелять, и я в этот момент как раз пришел в компанию)
там даже не понимали такие проблемы и не знали как решить, тут то мне мои боли и знания пригодились))
слава быдлокодерам, у нас будет больше работы!)))
@@it-sin9k ахаха)
2:49 обычно использую самописный хук useDebounce
Не игнорируйте экономию на спичках. На моем текущем проекте их сейчас столько рассыпано, что он только из них и состоит. Потихоньку разгребаю, уже ввёл несколько паттернов чтобы не плодить новые спички.
93 строчки
function debounce any>(fn: T, delay: number) {
let timeoutId: ReturnType | null = null
return function (...args: Parameters) {
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
fn(...args)
}, delay);
}
}
Можно уместить вот так
Спасибо за твои видео)
Зашел сюда чтобы написать этот комментарий)
проверку в clearTimeout можно не делать, оно скушает что угодно и даже не ругнётся
Это не отменяет того, что при анмаунте компонента таймер чиститься не будет
а если живёт таймер - то сохраняется ссылка на коллбэк, соответственно и сама функция со своим контекстом тоже жива) как минимум, вывод - утечка памяти)
Оо! Только заметил, что внутри debounce загрузка данных происходит - это привет рейсам
debound of 3 line
let id; // outside component
if (id) clearTimeout(id)
id = setTimeout(() => {}, 1000)
Отличный разбор!
Основное преимущество ваших разборов - это здравый смысл, объективность и желание разобраться со всеми реальными деталями вопроса.
Огромное спасибо за качественный инженерный контент!!!
спасибо за такой приятный комментарий) появилось больше желания делать новые разборы)
реакт головного мозга :D
никогда его не использовал. только свое решение.
Это ютуб премиум ник меняет))0)
Можно свой useDebounce написать легкий с одним useEffect и все , для кого то хватит и его )
да) в таких простых инструментах иногда лучше даже и свой написать, чем либу использовать)
Привет. Подскажи пожалуйста, а на сколько актуально и безопасно прокидывать сеттер из useState параметром в функцию? Как это сделал ты, чтобы вынести функцию за пределы компонента. Я просто как-то задумывался над таким паттерном. Это ведь хорошая возможность оставить в компоненте только одну разметку и стейты, вынеся все функции куда-нибудь рядом в utils.
У меня на работе был похожий кейс. Мы используем Redux и Redux-saga. И при диспатче экшена я прокинул сеттер закрытия модалки в сагу. После успешного окончания запроса я вызвал сеттер, чтобы закрыть модалку. Мне на ревью сказали, что так делать не следует. Пришлось создавать в редьюсере свойство, отвечающее за состояние открытия модалки. Что на мой взгляд выглядит более костыльно, а при усложнении логики, поддерживать это становится сложнее.
Так как я сделал со стейт функцией лучше особо не использовать. Мне стоило быть аккуратнее и вернуть результат обратно и там уже использовать стейт функцию. Основная проблема в том, что уже не будут многие знать, что эта функция принадлежит какому-то стейту. А может быть вообще ситуация, что компонент размаунчен, а кто то стейт обновляет. Что тоже есть проблема
Но и в redux хранить состояние модального окна я тоже считаю так себе вариантом :) Я планирую перезапустить пару видео про модльные окна и там поделюсь вариантами, как бы я делал)
@@it-sin9k Спасибо за ответ! Буду с нетерспением ждать новые видео про модалки! Если будут практические кейсы с redux-saga, будет вообще здорово)
Надеюсь следующим будет разбор состояния гонки (race condition) без использования хука useEffect.
Этот вопрос, имхо, куда интереснее.
можно и про это сделать) но там вроде не так все сложно)
@@it-sin9k я еще под первым видео пытался придумать адекватный вариант решения проблемы с состоянием гонки и ничего толкового не придумал, о чем там же и написал.
Вы, кстати, писали, что собираетесь записать видео по этому поводу, насколько я понял.
Прикрутить debounce к коду без useEffect не то чтобы сложно, в отличии от решения проблемы с race condition. По крайней мере для меня.
тогда точно запишу!)
Красава ! Никогда не понимал зачем жтот хук нужен (usedebounce), когда есть либо покет debounce либо метод из лодаша 😂
я делаю не 230 и даже не в 93 строки кода, а в 5 используя useEffect (так как мне нужно как правило работать с api). Просто запускаю setTimeout, если таймер не истек, обнуляю счетчик.
а мы как-то по старинке своё писали. Хотя учитывая размер меньше Кб, эт конечно того не стоило
Спасибо за ответ)
Говоря о denounce
Ви используете lodash?
Если lodash уже установлен в проект, тогда да. Использую debounce из lodash. Если же в проекте нет lodash, в таком случае использую один из способов: пишу свой простой deboucne, либо использую отдельный npm пакет debounce
Спасибо за видео. Но я все еще не понял как отменять в таком подходе предыдущее асинхронное действие. Разве что как то с abort controller делать.
Как пример, это просто и понятно работает в сагах с takeLatest
Цель этого видео не про это, но все же
запишу еще видео))
стоп, я один всегда сам реализовываю хук useDebounce и даже не думал о том, что существует отдельный пакет для этого?
получается так) но я тоже его никогда не использовал)
Спасибо за видео!
Зачем использовать либы для debounce, можно написать самому функцию debounce?
Да, можно и не использовать. Дело вкуса
спасибо за ваш труд
В даном примере можно не использовать дебаунс, достаточно использовать аборт контролер и передать его в фетч запрос. Таким образом, слишком частые запросы будут канселиться😊
ну канселить запросы на каждую букву, тоже такое себе) аборт контроллер нужен так же еще)
Это здорово что вы такое показываете.
Но интересно другое.
Сначала мы такие давайте пользоваться реактом и его фишками.
А потом не, давайте на обычном js функции с сет таймаутом писать.
Конечно в основе лежит js, но хотелось бы пользоваться только реактом как целостным решением,
а не городить свои, пусть даже более эффективные.
Ну тут вопрос про зоны ответственности :) Если речь идет, про отображение и lifecycle - это react, все остальное JS)
Я ожидал что будет какой то пример интересный с Suspense =)
Мы пока с простым не разобрались)
Спасибо большое!
🕶
Какие же у тебя классные видосы, жаль редко стали выходить.
спасибо! постараемся вернуть былой темп)
АйтиСиняк, приветствую
Не могу найти старый видос об модальных окнах, где вы рассказывали, как вы их делаете в вашей компании или что-то такое. Там описывались все действия от начала и до конца, мол нажимаем на кнопку, записываем это в URL и т.д.
Он удален?
да, скрыл это видео. Там старый роутер использовался и АПИшка немного поменялась. Поэтому подумываю пере записать эти видео
@@it-sin9k эх, мне сейчас как раз стало интересно пересмотреть это видео)
Буду ждать новое
Всё это конечно хорошо и тут сыглы+++ по поводу дебаунса, но релевантного примера с применением useEffect и без него так и не получили, всё ещё ждём
а почему не релевантный пример в видео?
@@it-sin9k Пример использования сам по себе чуть ли не анти-паттерн, если уж делать серию видосов про замену useEffect на что-то ещё, то и примеры хотелось бы видеть с актуальным использованием useEffect. Да и видос больше про дебаунс сам по себе, нежели useEffect, и раз уж о нём речь зашла, то неплохо было бы тут эту дебаунс обёртку в компоненте в useEvent обернуть, но это так, к слову
@@it-sin9k Пример всё тот же, по сути чуть ли не "антипаттерн" применения useEffect, всё же хотелось увидеть его реальное применение и альтернативы к нему, а видео вообще больше про дебаунс. Который неплохо было бы обернуть в useEvent, если уж в компоненте этот дебанус использовать, преимущественно начинающие ведь смотрят, как никак (и тут уже, возможно, напрашивается useDebounce хук, который будет внутри эту обёртку делать).
👍
Привет. Можно узнать с помощью чего ты делаешь анимации в видео?
С помощью After effects
вам ответил ниже человек, который монтировал все это)
Точно не с помощью useEffect видимо))
Первый
красава!
Мне кажется видосы надо снимать изначально более продумано и рассматривать проблему с разных сторон, чтобы донести свою позицию и найти в ней изъяны, если таковые имеются. Вторая часть намного лучше первой.
Спасибо!) идея моя изначальная "1 видео - 1 проблема". А если все собрать вместе, то это получится доклад)
@@it-sin9k в этом случае раздельно получается огрызок. Первое видео как будто из пальца высосано. Не информативно, зачем не понятно, почему так не ясно. В общем много вопросов вызывает.
Да, прикольно! Кстати, раньше я пихал useEffect везде где только можно было, но сейчас как-то всё поменялось и всё работает более предсказуемо.
Большинство с опытом бросают useEffect) так что это признак роста!) и спасибо за поддержку канала !!!
@@it-sin9k резонный вопрос: возможно ли существование современного React-приложения без useEffect? Вообще без него
Нихрена не понимаю в реакте, пишу на стандартном модульном джиэс. Я джаваскриптизер? Или просто невдуплятор?
это нормально) я понимаю в React но, не вдупляю как люди пишут на Angualr. Просто не знаком с ним, вот и не понимаю)
1. По-хорошему useEffect все равно нужен. Он необходим для очистки debounce таймера при анмаунте компонента.
2. Пример с выносом debounce функции из компонента работает только если она используется единственным экземпляром этого компонента. Если же ее вызывают несколько экземпляров, например инпутов в одной форме, то вызов той же функции на другом инпуте прервет последний таймер, вызванный предыдущим инпутом.
Спасибо за видео!
1. рассуждение теоретически полностью верные. Но на практике у debounce задержка 300 ms и это совсем редкий кейс, когда за это время может произойти unmount во время того, как пользователь пользуется инпутом из этого компонента. Поэтому я обычно игнорирую
2. опять же, если речь идет про инпут, не может человек печатать сразу в двух инпутах. Поэтому проблемы особой быть не должно. Да и случай пересечения, если пользователь умудрится это сделать не несет никакого урона. Поэтому получается разделение такого кейса, никто не оценит, а код станет сложнее)
Согласен, что кейс редкий и такой подход помимо ворнингов в консоли в большинстве случаев никак себя не проявит, особенно в контексте рассмотренных примеров в видео.
Однако, в тех редких случаях когда это стреляет - может быть крайне не приятно.
Откуда автор озвучки, что так шокает?
t=5m13s
из РБ :)
А шо токое?
вместо debounce лучше использовать p-debounce, вместо передачи setState можно просто заюзать промисы и потом просто getSomething().then(setState)
p-debounce не знаком с таким) надо изучить) с промисами так чаще и делается, но решил что в таком примере, так будет проще)