Как я работаю с debounce? "нет useEffect, нет бага" часть 2

Поделиться
HTML-код
  • Опубликовано: 29 июн 2024
  • Это вторая часть видео о наболевшем. В этот раз я поделюсь вариантом, как я работаю с debounce. На первый взгляд кажется тема не сложная, но исследование вышло очень интересное!
    Первая часть видео: • Нет useEffect, нет баг...
    Английская версия канала AI Bruise - / @ai-bruise
    ТГ канал - t.me/it_sin9k
    Поддержать Айти Синяка можно здесь:
    RUclips: / @it-sin9k
    boosty: boosty.to/sin9k
    Patreon: / itsin9k
    00:00 Анонс темы
    00:30 useDebounce
    01:24 useDebouncedCallback
    02:17 Из чего состоит use-debounce
    03:17 Используем пакет debounce
    04:25 Сравниваем кол-во рендеров
    05:18 Подведем итоги
    Подписаться на канал: / @it-sin9k
    Twitter: / it_sin9k

Комментарии • 102

  • @_GyG_
    @_GyG_ 4 месяца назад +52

    кто бы мог подумать, что простое видео про debounce станет убийцей пакета useDebounce! акции пакета рухнули))

    • @it-sin9k
      @it-sin9k  4 месяца назад +1

      ахахах) не думаю, что у меня такое большое влияние)

  • @adaltezze
    @adaltezze 4 месяца назад +22

    3:39 В случае, если функцию debounce использовать просто внутри компонента, то она будет отрабатывать неадекватно. При вызове onChange больше одного раза начнется мемори лик, т.к фунция updateSuggestions будет всегда новая, соответсвенно и таймеры в ней будут новые и никакой связи с предыдущем рендером не будет. Поэтому у каждого debounce должна быть функция cancel для ручной остановки таймера. Чтобы всего этого избежать, как вариант, можно вынести функцию debounce за компонент, как показано в видео, или же обернуть ее в useMemo. Что-то не верится, что с подобным ты не сталкивался)

    • @it-sin9k
      @it-sin9k  4 месяца назад

      спасибо за подробное описание проблемы! по хорошему конечно debounce функцию надо было еще в useCallback внутри компонента обернуть) но почему то опустил этот момент)

    • @adaltezze
      @adaltezze 4 месяца назад

      @@it-sin9k debounce лучше в useMemo оборачивать
      Хоть useCallback и запомнит ту функцию, что debounce вернёт при первом вызове, но после каждого ре-рендера debounce будет возвращать новую функцию, которая будет улетать в никуда

    • @andreyosipov7806
      @andreyosipov7806 4 месяца назад +3

      Выносить функцию за компонент как это предложил Синяк, это тоже костыль. В таком случае у нас один инстанс задебаунсенной функции с таймером на все инстансы компонента, а их может быть хоть сколько, что в целом нарушает изолированную работу отдельных инстансов друг от друга. Прям жутких багов в данном примере от этого не будет, но в каких-то других кейсах может крайне незаметно стрельнуть. Я считаю надо либо использовать useDebouncedCallback, либо использовать комбинацию debounce с useMemo. А ещё при анмаунте компонента надо отменять таймер дебаунса, поэтому все равно лучше заворачивать debounce в хук, чтобы всё сделать аккуратно

  • @user-gs7ro3tl9t
    @user-gs7ro3tl9t 4 месяца назад +3

    Отличный разбор!
    Основное преимущество ваших разборов - это здравый смысл, объективность и желание разобраться со всеми реальными деталями вопроса.
    Огромное спасибо за качественный инженерный контент!!!

    • @it-sin9k
      @it-sin9k  4 месяца назад +1

      спасибо за такой приятный комментарий) появилось больше желания делать новые разборы)

  • @Infinity-zf8ms
    @Infinity-zf8ms 4 месяца назад

    спасибо за ваш труд

  • @user-xd6sc7cr3x
    @user-xd6sc7cr3x Месяц назад

    Спасибо за ответ)

  • @TheTexPro
    @TheTexPro 4 месяца назад

    Спасибо большое!

  • @adaltezze
    @adaltezze 4 месяца назад +2

    Оо! Только заметил, что внутри debounce загрузка данных происходит - это привет рейсам

  • @Ramosok
    @Ramosok 4 месяца назад

    Какие же у тебя классные видосы, жаль редко стали выходить.

    • @it-sin9k
      @it-sin9k  4 месяца назад

      спасибо! постараемся вернуть былой темп)

  • @snatvb
    @snatvb 4 месяца назад +9

    а теперь берем и юзаем компонент в 2х местах
    берем и отмонтируем компонент, а дебаунс висит и шлет запрос :)

    • @it-sin9k
      @it-sin9k  4 месяца назад +3

      про race condition и unmount видимо должна быть отдельная история)

    • @snatvb
      @snatvb 4 месяца назад

      @@it-sin9k я думаю стоит рассказать
      у нас таким большая часть кода была обмазана и в итоге оно начало сильно стрелять, и я в этот момент как раз пришел в компанию)
      там даже не понимали такие проблемы и не знали как решить, тут то мне мои боли и знания пригодились))

    • @it-sin9k
      @it-sin9k  4 месяца назад +1

      слава быдлокодерам, у нас будет больше работы!)))

    • @snatvb
      @snatvb 4 месяца назад

      @@it-sin9k ахаха)

  • @user-xd6sc7cr3x
    @user-xd6sc7cr3x Месяц назад

    2:49 обычно использую самописный хук useDebounce

  • @tagnati5585
    @tagnati5585 4 месяца назад +2

    Не игнорируйте экономию на спичках. На моем текущем проекте их сейчас столько рассыпано, что он только из них и состоит. Потихоньку разгребаю, уже ввёл несколько паттернов чтобы не плодить новые спички.

  • @kasyak94
    @kasyak94 3 месяца назад

    Красава ! Никогда не понимал зачем жтот хук нужен (usedebounce), когда есть либо покет debounce либо метод из лодаша 😂

  • @koala2077
    @koala2077 4 месяца назад

    Я ожидал что будет какой то пример интересный с Suspense =)

    • @it-sin9k
      @it-sin9k  4 месяца назад

      Мы пока с простым не разобрались)

  • @nexus7172
    @nexus7172 4 месяца назад +1

    Надеюсь следующим будет разбор состояния гонки (race condition) без использования хука useEffect.
    Этот вопрос, имхо, куда интереснее.

    • @it-sin9k
      @it-sin9k  4 месяца назад

      можно и про это сделать) но там вроде не так все сложно)

    • @nexus7172
      @nexus7172 4 месяца назад

      ​@@it-sin9k я еще под первым видео пытался придумать адекватный вариант решения проблемы с состоянием гонки и ничего толкового не придумал, о чем там же и написал.
      Вы, кстати, писали, что собираетесь записать видео по этому поводу, насколько я понял.
      Прикрутить debounce к коду без useEffect не то чтобы сложно, в отличии от решения проблемы с race condition. По крайней мере для меня.

    • @it-sin9k
      @it-sin9k  4 месяца назад +3

      тогда точно запишу!)

  • @process-env
    @process-env 4 месяца назад +8

    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);
    }
    }
    Можно уместить вот так
    Спасибо за твои видео)

    • @boldureans
      @boldureans 4 месяца назад +1

      Зашел сюда чтобы написать этот комментарий)

    • @rs7xx
      @rs7xx 4 месяца назад +2

      проверку в clearTimeout можно не делать, оно скушает что угодно и даже не ругнётся

    • @philian73
      @philian73 Месяц назад

      Это не отменяет того, что при анмаунте компонента таймер чиститься не будет
      а если живёт таймер - то сохраняется ссылка на коллбэк, соответственно и сама функция со своим контекстом тоже жива) как минимум, вывод - утечка памяти)

  • @user-ku9md8kb4z
    @user-ku9md8kb4z 4 месяца назад

    👍

  • @user-zk6tw7rj1t
    @user-zk6tw7rj1t 4 месяца назад

    я делаю не 230 и даже не в 93 строки кода, а в 5 используя useEffect (так как мне нужно как правило работать с api). Просто запускаю setTimeout, если таймер не истек, обнуляю счетчик.

  • @user-lk4mb3nw3d
    @user-lk4mb3nw3d 4 месяца назад +1

    Можно свой useDebounce написать легкий с одним useEffect и все , для кого то хватит и его )

    • @it-sin9k
      @it-sin9k  4 месяца назад

      да) в таких простых инструментах иногда лучше даже и свой написать, чем либу использовать)

  • @vadymstebakov7023
    @vadymstebakov7023 4 месяца назад

    В даном примере можно не использовать дебаунс, достаточно использовать аборт контролер и передать его в фетч запрос. Таким образом, слишком частые запросы будут канселиться😊

    • @it-sin9k
      @it-sin9k  4 месяца назад

      ну канселить запросы на каждую букву, тоже такое себе) аборт контроллер нужен так же еще)

  • @kri4evskiy
    @kri4evskiy 4 месяца назад +2

    Да, прикольно! Кстати, раньше я пихал useEffect везде где только можно было, но сейчас как-то всё поменялось и всё работает более предсказуемо.

    • @it-sin9k
      @it-sin9k  4 месяца назад

      Большинство с опытом бросают useEffect) так что это признак роста!) и спасибо за поддержку канала !!!

    • @aysommer
      @aysommer 4 месяца назад

      ​@@it-sin9k резонный вопрос: возможно ли существование современного React-приложения без useEffect? Вообще без него

  • @nilsen1879
    @nilsen1879 4 месяца назад

    Спасибо за видео. Но я все еще не понял как отменять в таком подходе предыдущее асинхронное действие. Разве что как то с abort controller делать.
    Как пример, это просто и понятно работает в сагах с takeLatest
    Цель этого видео не про это, но все же

    • @it-sin9k
      @it-sin9k  4 месяца назад

      запишу еще видео))

  • @user-cg4jv8ny4j
    @user-cg4jv8ny4j 4 месяца назад

    Спасибо за видео!
    Зачем использовать либы для debounce, можно написать самому функцию debounce?

    • @it-sin9k
      @it-sin9k  4 месяца назад

      Да, можно и не использовать. Дело вкуса

  • @hasst9261
    @hasst9261 4 месяца назад

    АйтиСиняк, приветствую
    Не могу найти старый видос об модальных окнах, где вы рассказывали, как вы их делаете в вашей компании или что-то такое. Там описывались все действия от начала и до конца, мол нажимаем на кнопку, записываем это в URL и т.д.
    Он удален?

    • @it-sin9k
      @it-sin9k  4 месяца назад

      да, скрыл это видео. Там старый роутер использовался и АПИшка немного поменялась. Поэтому подумываю пере записать эти видео

    • @hasst9261
      @hasst9261 4 месяца назад

      @@it-sin9k эх, мне сейчас как раз стало интересно пересмотреть это видео)
      Буду ждать новое

  • @animekontororu9996
    @animekontororu9996 4 месяца назад

    Всё это конечно хорошо и тут сыглы+++ по поводу дебаунса, но релевантного примера с применением useEffect и без него так и не получили, всё ещё ждём

    • @it-sin9k
      @it-sin9k  3 месяца назад

      а почему не релевантный пример в видео?

    • @animekontororu9996
      @animekontororu9996 3 месяца назад

      @@it-sin9k Пример использования сам по себе чуть ли не анти-паттерн, если уж делать серию видосов про замену useEffect на что-то ещё, то и примеры хотелось бы видеть с актуальным использованием useEffect. Да и видос больше про дебаунс сам по себе, нежели useEffect, и раз уж о нём речь зашла, то неплохо было бы тут эту дебаунс обёртку в компоненте в useEvent обернуть, но это так, к слову

    • @animekontororu9996
      @animekontororu9996 3 месяца назад

      @@it-sin9k Пример всё тот же, по сути чуть ли не "антипаттерн" применения useEffect, всё же хотелось увидеть его реальное применение и альтернативы к нему, а видео вообще больше про дебаунс. Который неплохо было бы обернуть в useEvent, если уж в компоненте этот дебанус использовать, преимущественно начинающие ведь смотрят, как никак (и тут уже, возможно, напрашивается useDebounce хук, который будет внутри эту обёртку делать).

  • @xD-hu3gw
    @xD-hu3gw 4 месяца назад

    🕶

  • @tednaaa
    @tednaaa 4 месяца назад +2

    реакт головного мозга :D

  • @kronatankristof8804
    @kronatankristof8804 4 месяца назад

    а мы как-то по старинке своё писали. Хотя учитывая размер меньше Кб, эт конечно того не стоило

  • @user-xd6sc7cr3x
    @user-xd6sc7cr3x Месяц назад

    Это ютуб премиум ник меняет))0)

  • @zhanik753
    @zhanik753 4 месяца назад

    Привет. Можно узнать с помощью чего ты делаешь анимации в видео?

    • @set1k279
      @set1k279 4 месяца назад

      С помощью After effects

    • @it-sin9k
      @it-sin9k  4 месяца назад +1

      вам ответил ниже человек, который монтировал все это)

    • @alexpermenev
      @alexpermenev 4 месяца назад

      Точно не с помощью useEffect видимо))

  • @hasst9261
    @hasst9261 4 месяца назад

    Говоря о denounce
    Ви используете lodash?

    • @it-sin9k
      @it-sin9k  4 месяца назад +2

      Если lodash уже установлен в проект, тогда да. Использую debounce из lodash. Если же в проекте нет lodash, в таком случае использую один из способов: пишу свой простой deboucne, либо использую отдельный npm пакет debounce

  • @5paun
    @5paun 2 месяца назад

    Привет. Подскажи пожалуйста, а на сколько актуально и безопасно прокидывать сеттер из useState параметром в функцию? Как это сделал ты, чтобы вынести функцию за пределы компонента. Я просто как-то задумывался над таким паттерном. Это ведь хорошая возможность оставить в компоненте только одну разметку и стейты, вынеся все функции куда-нибудь рядом в utils.
    У меня на работе был похожий кейс. Мы используем Redux и Redux-saga. И при диспатче экшена я прокинул сеттер закрытия модалки в сагу. После успешного окончания запроса я вызвал сеттер, чтобы закрыть модалку. Мне на ревью сказали, что так делать не следует. Пришлось создавать в редьюсере свойство, отвечающее за состояние открытия модалки. Что на мой взгляд выглядит более костыльно, а при усложнении логики, поддерживать это становится сложнее.

    • @it-sin9k
      @it-sin9k  2 месяца назад

      Так как я сделал со стейт функцией лучше особо не использовать. Мне стоило быть аккуратнее и вернуть результат обратно и там уже использовать стейт функцию. Основная проблема в том, что уже не будут многие знать, что эта функция принадлежит какому-то стейту. А может быть вообще ситуация, что компонент размаунчен, а кто то стейт обновляет. Что тоже есть проблема
      Но и в redux хранить состояние модального окна я тоже считаю так себе вариантом :) Я планирую перезапустить пару видео про модльные окна и там поделюсь вариантами, как бы я делал)

    • @5paun
      @5paun 2 месяца назад

      ​@@it-sin9k Спасибо за ответ! Буду с нетерспением ждать новые видео про модалки! Если будут практические кейсы с redux-saga, будет вообще здорово)

  • @user-xr2dq4wi1r
    @user-xr2dq4wi1r 4 месяца назад

    Как насчёт такого примера?
    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
    }

    • @it-sin9k
      @it-sin9k  4 месяца назад

      можно и так) не хватает разве что ручек как почистить почистить программатикали

    • @oreen91
      @oreen91 4 месяца назад

      или так
      export function useDebounce(callback: () => void, deps: DependencyList, delay: number) {
      useEffect(() => {
      const timer = setTimeout(() => {
      callback()
      }, delay)
      return () => clearTimeout(timer)
      }, deps)
      }
      используем
      useDebounce(() => {
      }, зависимости)

    • @oreen91
      @oreen91 4 месяца назад

      export function useDebounce(callback: () => void, deps: DependencyList, delay: number) {
      useEffect(() => {
      const timer = setTimeout(callback, delay)
      return () => clearTimeout(timer)
      }, deps)
      })

  • @romanmed9035
    @romanmed9035 4 месяца назад

    никогда его не использовал. только свое решение.

  • @user-mh9pe4zp6l
    @user-mh9pe4zp6l 4 месяца назад

    стоп, я один всегда сам реализовываю хук useDebounce и даже не думал о том, что существует отдельный пакет для этого?

    • @it-sin9k
      @it-sin9k  4 месяца назад

      получается так) но я тоже его никогда не использовал)

  • @chikenmacnugget
    @chikenmacnugget 4 месяца назад +3

    Мне кажется видосы надо снимать изначально более продумано и рассматривать проблему с разных сторон, чтобы донести свою позицию и найти в ней изъяны, если таковые имеются. Вторая часть намного лучше первой.

    • @it-sin9k
      @it-sin9k  4 месяца назад

      Спасибо!) идея моя изначальная "1 видео - 1 проблема". А если все собрать вместе, то это получится доклад)

    • @chikenmacnugget
      @chikenmacnugget 4 месяца назад +2

      @@it-sin9k в этом случае раздельно получается огрызок. Первое видео как будто из пальца высосано. Не информативно, зачем не понятно, почему так не ясно. В общем много вопросов вызывает.

  • @MikeNovikov
    @MikeNovikov 4 месяца назад

    1. По-хорошему useEffect все равно нужен. Он необходим для очистки debounce таймера при анмаунте компонента.
    2. Пример с выносом debounce функции из компонента работает только если она используется единственным экземпляром этого компонента. Если же ее вызывают несколько экземпляров, например инпутов в одной форме, то вызов той же функции на другом инпуте прервет последний таймер, вызванный предыдущим инпутом.
    Спасибо за видео!

    • @it-sin9k
      @it-sin9k  4 месяца назад +1

      1. рассуждение теоретически полностью верные. Но на практике у debounce задержка 300 ms и это совсем редкий кейс, когда за это время может произойти unmount во время того, как пользователь пользуется инпутом из этого компонента. Поэтому я обычно игнорирую
      2. опять же, если речь идет про инпут, не может человек печатать сразу в двух инпутах. Поэтому проблемы особой быть не должно. Да и случай пересечения, если пользователь умудрится это сделать не несет никакого урона. Поэтому получается разделение такого кейса, никто не оценит, а код станет сложнее)

    • @MikeNovikov
      @MikeNovikov 4 месяца назад

      Согласен, что кейс редкий и такой подход помимо ворнингов в консоли в большинстве случаев никак себя не проявит, особенно в контексте рассмотренных примеров в видео.
      Однако, в тех редких случаях когда это стреляет - может быть крайне не приятно.

  • @TestTest-cr2xb
    @TestTest-cr2xb 4 месяца назад

    Первый

    • @it-sin9k
      @it-sin9k  4 месяца назад

      красава!

  • @POEOneLove
    @POEOneLove 4 месяца назад

    Нихрена не понимаю в реакте, пишу на стандартном модульном джиэс. Я джаваскриптизер? Или просто невдуплятор?

    • @it-sin9k
      @it-sin9k  4 месяца назад

      это нормально) я понимаю в React но, не вдупляю как люди пишут на Angualr. Просто не знаком с ним, вот и не понимаю)

  • @mrstronciy1060
    @mrstronciy1060 4 месяца назад

    Откуда автор озвучки, что так шокает?
    t=5m13s

  • @mulfyx
    @mulfyx 4 месяца назад

    вместо debounce лучше использовать p-debounce, вместо передачи setState можно просто заюзать промисы и потом просто getSomething().then(setState)

    • @it-sin9k
      @it-sin9k  4 месяца назад

      p-debounce не знаком с таким) надо изучить) с промисами так чаще и делается, но решил что в таком примере, так будет проще)

  • @user-wi2hl7mn7r
    @user-wi2hl7mn7r Месяц назад

    debound of 3 line
    let id; // outside component
    if (id) clearTimeout(id)
    id = setTimeout(() => {}, 1000)

  • @user-ud4bj9vc7l
    @user-ud4bj9vc7l 2 месяца назад

    Это здорово что вы такое показываете.
    Но интересно другое.
    Сначала мы такие давайте пользоваться реактом и его фишками.
    А потом не, давайте на обычном js функции с сет таймаутом писать.
    Конечно в основе лежит js, но хотелось бы пользоваться только реактом как целостным решением,
    а не городить свои, пусть даже более эффективные.

    • @it-sin9k
      @it-sin9k  2 месяца назад

      Ну тут вопрос про зоны ответственности :) Если речь идет, про отображение и lifecycle - это react, все остальное JS)