Reselect не так прост как кажется!

Поделиться
HTML-код
  • Опубликовано: 29 ноя 2024

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

  • @nexus7172
    @nexus7172 2 года назад +33

    Не угадал, но если подумать, то все становится очевидно.
    Мемоизированный selectSubtotal вызывается дважды: из selectTotal и selectTax, т.е. время на чтение данных из памяти нужно умножать на 2, это будет уже 1.72 секунды.
    Т.к. теперь каждую итерацию нужно считать значения для двух селекторов (selectTotal и selectTax), то и временные расходы на это будут больше, чем на расчет значения лишь одного значения для селектора selectTotal.
    Если на подсчет значения селектора selectTotal уходит 0.28 секунды, то 0.13 секунды уходит на подсчет значения селектора selectTax.

    • @it-sin9k
      @it-sin9k  2 года назад +1

      Закрепил :)

    • @MDFireX5
      @MDFireX5 Год назад

      фиговое обьяснение.

    • @astkh4381
      @astkh4381 5 месяцев назад

      @@MDFireX5 ну так объясни лучше.

  • @zafarkhamidullaev2067
    @zafarkhamidullaev2067 2 года назад +14

    Хотелось бы видео про микрофронтенды с использованием single-spa. До сих пор не могу найти годный контент про эту тему

    • @it-sin9k
      @it-sin9k  2 года назад +3

      Последние пол года пилю проект с использование ModuleFederation для микрофронтов) там действительно есть свои нюансы

    • @Catafey1
      @Catafey1 2 года назад +3

      Расскажи тогда про moduleFederation

    • @it-sin9k
      @it-sin9k  2 года назад

      @@Catafey1 Очередь из тем уже очень велика) сам иногда не знаю, какую следующую возьму тему)

    • @cikada3398
      @cikada3398 2 года назад +7

      @@it-sin9k бери moduleFederation :)

    • @it-sin9k
      @it-sin9k  2 года назад

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

  • @dimitro.cardellini
    @dimitro.cardellini 2 года назад +2

    Привет! ;)
    Все не совсем так, как рассказал АйТи-Синяк ...
    Весь подвох заключается в последнем селекторе selectTotal. Именно он испортил бенчмарк, и именно его надо было оставлять единственным мемоизированным.
    Итак, по порядку:
    const selectTotal = createSelector(
    selectSubtotal,
    selectTax,
    (subtotal, tax) => ({ total: subtotal + tax })
    )
    Обратите внимание, что собственно последний селектор возвращает не просто число, а возвращает объект. Если мы selectTotal переписываем без createSelector:
    const selectTotal = state => ({ total: selectSubtotal(state) + selectTax(state) });
    то, каждый вызов этого селектора будет возвращать новый объект (новую ссылку). Кстати, если мы начинаем создавать 1 миллиард объектов, то мы нагрузим свой движок упражнениями по управлению памятью, а еще де-оптимизируем наш бенчмарк (в случае с мемоизацией там вообще могло дойти до возвращения константы после, скажем, 100 тыс. итерации).
    Ну, и далее очевидно, если у нас будет компонент подписанный на selectTotal, то он будет рендериться при каждом dispatch-е, даже если state не менялся (ага, dispatch вызывает подписчиков даже, если state не поменялся: github.com/reduxjs/redux/blob/master/src/createStore.ts#L257).
    Собственно, reselect не столько про производительность, сколько про мемоизацию ссылок и предотвращение лишних рендеров.
    Так, что createSelector очень даже надо использовать для селекторов, возвращающих ссылки.

    • @it-sin9k
      @it-sin9k  2 года назад

      В данном видео, я рассматривал reselect в вакуме. Чтобы показать людям, что он не так предсказуем как кажется. Видео о reselect в рамках React, будет еще отдельным видео. Где конечно обсудим лишние рендеры :)

    • @dimitro.cardellini
      @dimitro.cardellini 2 года назад

      @@it-sin9k как работает реселект очень хорошо показано -- єто бесспорно.
      Но, все же давать оценку, что он не так предсказуем, без понимания того, для чего он нужен -- это несколько ... поспешно.
      Так, что ждем новое видео )

  • @kostyakozlov5289
    @kostyakozlov5289 2 года назад

    браво, думал раскидаю все примеры. В итоге вынес много нового

  • @S.O..K.
    @S.O..K. Год назад

    Я думаю, что дорогой является не взятие мемоизированного значения и не вычисление, а операция сравнения для того чтоб понять надо ли брать мемоизированное значение или надо проводить вычисление. Тоесть если взять третий пример (где selectTotal) не мемоизирован, то операция сравнения входных данных для проверки совпадают ли они с мемоизированным входом забрали львиную часть времени на вычисления...

    • @it-sin9k
      @it-sin9k  Год назад

      не очень уловил идею. Сравнение же идет по простому тройному ровно. Как это может быть дорого?

  • @ТалгатМуканов-б5ж

    так легко все оказывается! я все понял, спасибо

  • @levsonc
    @levsonc 2 года назад +1

    С мемоизацией такая интересная штука, что выполнение в миллионную долю секунды - это очень быстро. Погрешность во времени выполнении на таких значениях несущественна. Зато взяв за правило всё мемоизировать вы с меньшей вероятностью столкнётесь с ситуацией, что у вас тормозит приложение из-за неэффективной работы в каких-то случаях. Такая стратегия эффективна для промышленной разработки. Единственная причина не делать этого - писать меньше кода. И тут наиболее интересным представляется проект React Forget, который по идее возьмёт это на себя. Правда, в случае Редакса, возможно, нужна будет отдельная реализация или плагин.

    • @it-sin9k
      @it-sin9k  2 года назад

      Хороший комментарий, эти темы планирую затронуть в следующем видео про reselect :)

  • @brightisrael1304
    @brightisrael1304 2 года назад

    Спасибо за подробный обзор Reselect

  • @prog-hak
    @prog-hak 2 года назад

    опять шик блеск, глубоко и с разумом, спасибо 👍

  • @d0paminer
    @d0paminer 2 года назад +1

    кажется, в реакт приложении это всё таки имеет смысл:
    из redux доки:
    "when an action is dispatched to the Redux store, useSelector() only forces a re-render if the selector result appears to be different than the last result. The default comparison is a strict === reference comparison. With useSelector(), returning a new object every time will always force a re-render by default...
    Call useSelector() multiple times, with each call returning a single field value
    If you want to retrieve multiple values from the store, you can Use Reselect or a similar library to create a memoized selector that returns multiple values in one object, but only returns a new object when one of the values has changed.
    Пример с 99999999 пересчетами не актуален в веб-приложениях. А перерендеры - это актуально.

    • @it-sin9k
      @it-sin9k  2 года назад +1

      Про рендеры и т.д. будет в следующей части реселкта :)
      а по поводу работы useSelector, мы это уже рассмотрели в предыдущих видео:
      ruclips.net/video/SVG-x-4BQic/видео.html

  • @AlefersXaoC
    @AlefersXaoC Год назад

    Привет. У меня вопрос, до ответа на который у меня разобраться не получается.
    У меня есть переменая, значения для которой я принимаю из createSelector передавая туда несколько других значений. Например это функция перевода, обёрнутая в createSelector, которая получает на вход объект фраз, ключи пути, фразу и объект переменных для вставки через замеру в результирующую строку. Теперь во всех местах где используется перевод я использую получается мемоизацию... но в упор не понимаю где хранится предыдущее состония вычисления и сама функция, если при просчёте компонента внутри переменной уже строковое значение.
    const translate = createSelector( парамсы => код и возврат результата)
    const phrase = translate(парамсы) - при это фраза это строка и в себе функцию не содержит, где тогда хранится предыдущее вычисленное значение?

  • @kacetal
    @kacetal 2 года назад

    Очень нравятся ваши видео, но если честно пример не очень корректный. Вы ведь не на бэкенде используете реселект с миллиардом операций. Думаю потеря времени на ререндер будет гораздо существенней чем экономя времени на кеширования реселект.
    Ps: не уверен что правильно именно так делать бенчмарк, разве у js нет таких понятий как прогрев? Или компиляция часто используемых функций в машинный код, инлайнинг итд.

    • @it-sin9k
      @it-sin9k  2 года назад

      Все верно про рендер. В рамках этого видео, хотел показать, что мемоизация не бесплатная на реальных цифрах. Да и показать, что иногда кажется, очевидно как работает инструмент, а на самом деле все немного сложнее. Чтобы показать всю историю с рендерами, я записал еще одно видео, показывающее какую именно проблему решает реселект:
      ruclips.net/video/tbfo28Q5eag/видео.html
      И собственно сейчас готовлю видео, про сам реселект в рамках React
      По поводу измерений, было много комментариев на эту тему, поэтому записал еще одно видео с более точными бенчмарками:
      ruclips.net/video/ZVSJckibKe8/видео.html

  • @kawaikaino5277
    @kawaikaino5277 2 года назад +1

    Использую реселект, только когда компонент использует UseSelector - в таком случае удается не перерендеривать компонент использующий UseSelector

  • @MrMultiCrafting
    @MrMultiCrafting 2 года назад +1

    Представлена не совсем корректная схема работы селектора. Когда селектор вызывается повторно то он начинает процедуру сравнения данных из входящих селекторов и тем самым запрашивает данные у них триггеря туже процедуру. В итоге когда повторно вызывается selectTotal по прежнему вызываются и все остальные селекторы.
    Поэтому возникает такая разница между 3 и 0 селекторов, на проверку данных тратиться больше времени чем на их вычисление.
    Но когда количество итемов увеличилось до 10 то вычисления стали занимать больше времени чем их проверка и результат конкретно изменился при отсутствии селекторов. А при наличии селекторов время не изменилось так как айтемы считались один раз, а всё остальное время работали одинаковые проверки на схожесть входных данных и разница одного вычисления между 2 и 10 айтемами незначительна.

    • @MrMultiCrafting
      @MrMultiCrafting 2 года назад

      Также замечу что если селектор создаёт объект или массив то возврат замемоизированного значения сыграет в плюс так как лишит лишних ререндеров из-за того что useSelector будет возвращать хоть и такой же но новый объект/массив.
      Поэтому пример из документации будет работать производительнее так как он в конце возвращает замемоизированный объект и не будет ререндерить компонент))

    • @MrMultiCrafting
      @MrMultiCrafting 2 года назад

      Также вот пару основных признаков что вам нужно обернуть в createSelector:
      1) У вас в селекторе производиться большое количество вычислений(много слаживаете, перемножаете, делите, объединяете строки)
      2) Если у вас в селекторе есть цикл то в 99.99% случаев он делает больше действий чем проверка входных данных и мемоизация будет благом
      3) если селектор возвращает массив или объект который был создан в теле селектора. Зачастую там будут возвращаться одинаковые но идентичные сущности.

    • @MrMultiCrafting
      @MrMultiCrafting 2 года назад

      И лайфхак в догонку:
      Если у вас в селекторе есть что-то типа такого:
      return myList || [];
      Или такого:
      return myObj || {};
      То лучше вместо того чтобы создавать пустые сущности положите в константы EMPTY_ARR и EMPTY_OBJ и возвращайте их. Это может спасти вас от кучи лишних пересчётов селекторов и ререндеров.

    • @azad0808
      @azad0808 2 года назад

      @@MrMultiCraftingа как селектор понимает что данные не изменились получеется каждый раз проводит глубокое сравнивение?, а это ведь ресурсоемко.

    • @MrMultiCrafting
      @MrMultiCrafting 2 года назад

      @@azad0808 нет, он сравнивает их по тождественности. Поэтому и нужно мемоизировать возвращаемые объекты и массивы чтобы другие селекторы не перевыполнялись

  • @yuryitikhonoff9631
    @yuryitikhonoff9631 2 года назад

    Спасибо за контент. Я вообще-то думал, что реселект нужен для оптимизации рендеров Реакта за счёт мемоизаиции селекторов, а не для кеширования вычислений. Но референс на доку снимает все вопросы )))

    • @it-sin9k
      @it-sin9k  2 года назад +2

      Значит не зря делал это видео)

  • @Дмитрий-ч3д9ю
    @Дмитрий-ч3д9ю 2 года назад

    Я после этого выпуска прослезился и таки подписался на патреон)

    • @it-sin9k
      @it-sin9k  2 года назад +1

      Спасибо за подписку!!!

  • @kyzinatra6391
    @kyzinatra6391 Год назад

    А почему нельзя просто в реакте производить нужные вычисления из под useMemo к примеру. Тогда вы будем использовать только простые селекторые и в них самих ничего делать не будем. Следовательно не будет лишних вызовов из редукса. А лишние пересчеты самого реакта мы отметаем либо useMemo либо ключами при list rendering

    • @it-sin9k
      @it-sin9k  Год назад +1

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

  • @armensargsyan8981
    @armensargsyan8981 Год назад

    у меня только один вопрос, не как не могу понять зачем использовать что то не по назначению, а патом жаловатся на проблемы?) useSelector (СЛОВО selector) не филр, не мутатор, не что то еще а именно selector... что касается удобства, в чем проблема отдельно отфилтровать? или создать хук в катором это делать? (это косательно реакт)

    • @it-sin9k
      @it-sin9k  Год назад

      зачаствую нам нужен вычисленный селектор в многих местах. Например totalPrice. Не хочется ее вычислять несколько раз в разных местах. Куда удобнее посчитать 1 раз и в остальных местах отдавать значение из кэша

    • @armensargsyan8981
      @armensargsyan8981 Год назад

      @@it-sin9k а что мешает в initialState добавить totalPrice? и в редюсере при добавлении в массив price новой цены обновить totalPrice, потому как примая зависимость totalPrice prices

    • @it-sin9k
      @it-sin9k  Год назад

      ​@@armensargsyan8981 От данного подхода давно отказались. У него есть 2 проблемы:
      - totalPrice может высчитываться из допустим 5-6 параметров (цены, скидки, время суток и т.д.). В итоге на любой из экшенов обновления любого из полей надо обновлять и totalPrice. А если еще какой-то из полей надо хранить в каком то другом редьюсере, то нужно в экшен еще пробрасывать значение всех нужных полей. В итоге увеличивается шанс, что totalPrice будет не настоящий. Поэтому все и используют вычисляемый селектор, вместо хранения. Чтобы избежать нескольких источников истины и их синхронизации
      - 2-ая проблема, это получится, что в reducer будет хранится какая то бизнес логика. А от этого давно вроде все решили избавляться. Не местой бизнес логике в редьюсере

    • @armensargsyan8981
      @armensargsyan8981 Год назад

      @@it-sin9k можно все это обойти, во первых вынести totalPrice в отдельный reducer (конечно ломается логическая взоимосвяз между prices totalPrice что плохо, но думаю приемлимо, так как он зависит еще и от 5-6 полей которые в других редюсерах), и что то новое но все же(использовать некий вычислительный компонент(watcher) который нечего не рендерит а только диспатчит totalPrice при изменении 5-6 пропсов): по итогу вычисляется только единожды, и все работает как задумано. НО огромное спасибо!!! за обяснение что как, понял смысл селекторов и createSelector-а, еще раз спасибо!

  • @boldureans
    @boldureans 2 года назад +1

    Я тоже не угадал) Спасибо за видео, Саш. Можешь снять про recoilJS? Она еще находится у меты под тегом экспериментального см, но выглядит уж очень любопытно 👀

    • @it-sin9k
      @it-sin9k  2 года назад +1

      Да, у меня список на изучение стоит recoil, zustand, effector)) Только обозревать все это не так просто и быстро)

    • @boldureans
      @boldureans 2 года назад

      @@it-sin9k понимаю) мало того, что нужно оформить кейс, еще и посмотреть как это устроено внутри)
      zustand самый мемный как по мне)

    • @boldureans
      @boldureans 2 года назад

      @@it-sin9k но recoil вроде как прям очевидный, у них классное видео на превью странице, которое четко описывает задачу.

    • @it-sin9k
      @it-sin9k  2 года назад +1

      Самое интересное, что мне про каждую из технологий, так кто то пишет) что одна технология это такое) а вот другая, намного интереснее)

    • @boldureans
      @boldureans 2 года назад

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

  • @valentynlugovyi4789
    @valentynlugovyi4789 2 года назад

    Wtf, а что насчёт лишних ререндоров, я так понял их не будет но только если мы будет возвращать наружу значение которое было взято из createSelector. Эта тема не была поднята в видео но я считаю что это важно.

    • @it-sin9k
      @it-sin9k  2 года назад

      Все верно :)
      Про это будет отдельное видео

    • @valentynlugovyi4789
      @valentynlugovyi4789 2 года назад

      Кстати есть также возможность добавить колбек снаружи в useSelector-e вторым параметром, разве это тоже не своего рода мемоизация?
      Тот колбек должен отвечать за перерисовку компоненты... А видео больше про оптимизацию селекторов... Хотя тот колбек своего рода тоже оптимизация))
      Интересно мнение автора.

    • @it-sin9k
      @it-sin9k  2 года назад

      @@valentynlugovyi4789 Про useSelector у нас есть отдельное видео, где как раз упоминается второй параметр (ruclips.net/video/SVG-x-4BQic/видео.html). По факту вы правы, этот колбек нужен именно для упразднения лишних рендеров :)

  • @d_r_robot
    @d_r_robot 2 года назад

    Спасибо!

  • @Ramosok
    @Ramosok 2 года назад

    Super!

  • @pluto2656
    @pluto2656 Год назад

    Вердикт: забыть вместе с redux

  • @ВалерийСмирнов-у9ш
    @ВалерийСмирнов-у9ш 2 года назад +3

    Этот бенчмарк не отражает действительности из-за оптимизаций v8

    • @it-sin9k
      @it-sin9k  2 года назад

      Какой-то конкретной оптимизации?

    • @grandphone3585
      @grandphone3585 2 года назад

      @@it-sin9k ruclips.net/video/HPFARivHJRY/видео.html
      Хорошее видео про оптимизацию и бенчмарки

    • @it-sin9k
      @it-sin9k  2 года назад +3

      @@grandphone3585 да, я знаком с подобного рода видео. Они очень поучительны. И конечно я не могу гарантировать, что мои бенчмарки правдивы. Но и говорить, что именно эти бенчмарки не отражают действительность мы тоже не можем.
      Далее напрашивается вопрос, а почему же я в итоге решил записать подобного рода видео. Основной мотивацией служили косвенные признаки. Я делал разного рода бенчмарки, и общая динамика отражала, идеи которые заложены в этом коде. Да действительно возможно 99_999_999 выполняются за иное количество времени, чем у меня в видео, но мне тут были важны не абсолютные значения, а скорее общая динамика (быстрее или медленее код выполняется). Поэтому для меня это было убедительным доводом, чтобы записать такого рода видео

    • @ВалерийСмирнов-у9ш
      @ВалерийСмирнов-у9ш 2 года назад

      @@it-sin9k я не спорю с относительными цифрами. Просто в видео подчеркивается, что все выполняется очень быстро хотя так как функций не имеют сайд эффектов, то v8 их выполнил только несколько раз. А в случае с функциями без мемоизации у него хуже вышло
      В целом из ролика следует, что преждевременной оптимизацией заниматься не стоит

    • @ЮрийБондаренко-у7е
      @ЮрийБондаренко-у7е 2 года назад

      А добавь плиз eval(' ') в свои функции, чтобы наверняка отключить оптимизации, и покажи новые числа.

  • @mr.nikita
    @mr.nikita 2 года назад

    Данная библиотек необходима для предотвращения лишних ререндоров.

    • @kawaikaino5277
      @kawaikaino5277 2 года назад

      я тоже ее только для этого использую... Т.к при использовании голого UseSelector компонент всегда ререндерится

    • @azad0808
      @azad0808 2 года назад

      Если что в useSelector можно предотвратить ререндер сравнением стейтов

    • @mr.nikita
      @mr.nikita 2 года назад

      @@azad0808, немного не понял. Хотелось бы увидеть example

    • @azad0808
      @azad0808 2 года назад

      @@mr.nikita прочитай в официальной доке про 2 аргумент хука, "equalityFn"

    • @mr.nikita
      @mr.nikita 2 года назад

      @@azad0808 Спасибо. Не знал об этом

  • @NoName-zh7cc
    @NoName-zh7cc 2 года назад

    Лучшие видосы по реакту на русском языке

    • @it-sin9k
      @it-sin9k  2 года назад

      Спасибо! Мы очень стараемся!)

  • @hihoho1578
    @hihoho1578 2 года назад +1

    когда уже эта поделка (редакс) уступит место чему-то более адекватному реальности?)

    • @it-sin9k
      @it-sin9k  2 года назад

      например чему?)

    • @hihoho1578
      @hihoho1578 2 года назад

      ​@@it-sin9k использую эффектор 3+ года в проде, полёт отличный.
      жаль что он только в узких кругах знаком и в ещё более узких используется)

    • @it-sin9k
      @it-sin9k  2 года назад

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

    • @hihoho1578
      @hihoho1578 2 года назад

      @@it-sin9k ждём обзор)

    • @levsonc
      @levsonc 2 года назад

      @@hihoho1578 Ну вот Ситнику настолько не понравились отдельные проблемы эффектора (практически нельзя уменьшить размер), что даже написал свой Наностор.

  • @redgreengrey
    @redgreengrey 2 года назад

    не угадал

  • @azad0808
    @azad0808 2 года назад

    вместо new Date лучше использовать performance.now()

    • @it-sin9k
      @it-sin9k  2 года назад

      А оно в ноде то ли не работало, то ли еще что-то. Я уже не помню точно)

    • @azad0808
      @azad0808 2 года назад

      @@it-sin9k он в ноде под perf_hooks.performance находится

    • @it-sin9k
      @it-sin9k  2 года назад +2

      @@azad0808 попробую обязательно)