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

Поділитися
Вставка
  • Опубліковано 4 сер 2024

КОМЕНТАРІ • 98

  • @nexus7172
    @nexus7172 2 роки тому +32

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

    • @it-sin9k
      @it-sin9k  2 роки тому +1

      Закрепил :)

    • @MDFireX5
      @MDFireX5 8 місяців тому

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

    • @astkh4381
      @astkh4381 Місяць тому

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

  • @prog-hak
    @prog-hak 2 роки тому

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

  • @brightisrael1304
    @brightisrael1304 2 роки тому

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

  • @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 роки тому +6

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

    • @it-sin9k
      @it-sin9k  2 роки тому

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

  • @kostyakozlov5289
    @kostyakozlov5289 2 роки тому

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

  • @d_r_robot
    @d_r_robot 2 роки тому

    Спасибо!

  • @yuryitikhonoff9631
    @yuryitikhonoff9631 2 роки тому

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

    • @it-sin9k
      @it-sin9k  2 роки тому +2

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

  • @Ramosok
    @Ramosok 2 роки тому

    Super!

  • @user-ju3tw3vb3o
    @user-ju3tw3vb3o Рік тому

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

  • @dimitro.cardellini
    @dimitro.cardellini Рік тому +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  Рік тому

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

    • @dimitro.cardellini
      @dimitro.cardellini Рік тому

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

  • @user-cc4jp1xl6w
    @user-cc4jp1xl6w 2 роки тому +1

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

    • @it-sin9k
      @it-sin9k  2 роки тому +1

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

  • @levsonc
    @levsonc 2 роки тому +1

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

    • @it-sin9k
      @it-sin9k  2 роки тому

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

  • @kawaikaino5277
    @kawaikaino5277 2 роки тому +1

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

  • @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, мы это уже рассмотрели в предыдущих видео:
      ua-cam.com/video/SVG-x-4BQic/v-deo.html

  • @AlefersXaoC
    @AlefersXaoC Рік тому

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

  • @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 интереснее не значит эффективная) каждая решает какие-то свои задачи, а превью вроде как всегда показывает самые лучшие юз кейсы без случаев когда НЕ стоит её использовать как мы узнали из сегодняшнего видео)

  • @S.O..K.
    @S.O..K. Рік тому

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

    • @it-sin9k
      @it-sin9k  Рік тому

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

  • @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 нет, он сравнивает их по тождественности. Поэтому и нужно мемоизировать возвращаемые объекты и массивы чтобы другие селекторы не перевыполнялись

  • @kacetal
    @kacetal Рік тому

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

    • @it-sin9k
      @it-sin9k  Рік тому

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

  • @kyzinatra6391
    @kyzinatra6391 Рік тому

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

    • @it-sin9k
      @it-sin9k  Рік тому +1

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

  • @valentynlugovyi4789
    @valentynlugovyi4789 2 роки тому

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

    • @it-sin9k
      @it-sin9k  2 роки тому

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

    • @valentynlugovyi4789
      @valentynlugovyi4789 2 роки тому

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

    • @it-sin9k
      @it-sin9k  2 роки тому

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

  • @pluto2656
    @pluto2656 Рік тому

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

  • @user-ce9ff2rj4o
    @user-ce9ff2rj4o 2 роки тому +3

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

    • @it-sin9k
      @it-sin9k  2 роки тому

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

    • @grandphone3585
      @grandphone3585 2 роки тому

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

    • @it-sin9k
      @it-sin9k  2 роки тому +3

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

    • @user-ce9ff2rj4o
      @user-ce9ff2rj4o 2 роки тому

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

    • @user-jh2id4qf5w
      @user-jh2id4qf5w 2 роки тому

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

  • @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-а, еще раз спасибо!

  • @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 Спасибо. Не знал об этом

  • @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 роки тому

    не угадал

  • @NoName-zh7cc
    @NoName-zh7cc 2 роки тому

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

    • @it-sin9k
      @it-sin9k  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 попробую обязательно)