Comments 52
Получается что producer от immer идет в редьюсер, а Memoize-state в mapStateToProps.

Не очень понятно с beautiful-react-redux — что значит:
молча обернет mapStateToProps два раза в memoize-state… (до свидания re-reselect)

Хотелось бы более подробной информации.

За библиотеку спасибо
У reselect есть одна проблема — он помнит только один последний результат.
Если у вас есть два инстанса компонента, то в начале первый что-то возьмет из state на основе своих props, а потом второй, а потом опять первый. И всегда кеш будет чистый, так как то что там храниться — «не подходит».
Полуофициальное решение проблемы — re-reselect, который позволяет указать как «разделять» компоненты.
Второе полуофициальное решение — завернуть createSelector в замыкание, так чтобы проблемы с кешом не будет. Но тогда они не смогут «шарить» кеш между инстансами.
beautiful-react-redux оборачивает mapStateTopProps в memoize-state «снаружи», и еще раз «внутри». Те для каждого отдельного элемента, и для всех целиком, на случай если разницы между ними нет.
В общем универсальное решение.
Еще пару вопросов:

  1. Необходимость писать много селекторов используя reselect дает неплохую инкапсуляцию — каждый контейнер имеет свои специфические селекторы, а другие просто их используют, ничего не зная о реальных путях в state. Что рекомендуется в случае Memoize-state?
  2. Proxy создается на каждый вызов mapStateToProps?
1. Можно использовать селекторы. Можно использовать просто различные helper функции без мемоизации. Можно использовать вообще все что угодно, и оно будет работать, но не всегда эффективно, посколько рано или поздно мемоизация в reselect «скроет» доступ к конкретным значениям, и memoize-state начнет агриться на более «высокоуровневые» значения.

2. И да и нет. Для того чтобы возможная «другая» мемоизация работала требуется предоставлять «одинаковые» обьекты завернутые в «однаковые» прокси. В общем там внутри все созданные прокси храняться в WeakMap, и без надобности не создаются.

Почему нет — потому что сам state между вызовами будет разный, и для него прокси будет создаваться каждый раз.
Как говорилось выше — без проблем пару миллионов в секунду.

Забавно, как React с помощью обвесов превращается в дедушку Knockout. Забава в том, что изначально pull-концепция (вытягивания данных всеми ветками из корня модели) была выбрана как более простая против push-концепции (проталкивание данных из корня в ветки, которые задеты изменениями), вместо запутанных и многословных observables (см. Knockout-mapping). Мол, у нас виртуальный DOM, он сам отметёт то, что не нужно менять, не заморачивайтесь (640 килобайт хватит всем). Создатель Vue.js в этом смысле оказался чуть прозорливее.

Это все еще pull, просто у любого инструмента есть свои ограничения. Я так думаю где-то глубоко в глубинах Vue живет что-то похожее, ну а в MobX (@computed) живет вообще почти что тоже самое.
В итоге и получается, что и коробки Vue, да и Angular, могут работать сильно быстрее. Ну просто потому что програмисты такие програмисты. Глаз за глаз за ними нужен. Ну или костыль. И memoize-state – фабрика костылей. Подопрет где нужно и все окей.

Изначально концепция редукса — именно push. Селекторы просто вырезают из огромного стейта кусочек. Собственно это та же концепция, что и у RxJS — у вас есть стрим глобального состояния. И есть зависимые стримы, получаемые маппингом и фильтрацией оригинального. И пока кто-то не запушил данные, вы не можете их отфильтровать. А вот концепция knockout/mobx/vue как раз pull — пока вы данные не запросили, никто их и готовить не станет, а как запросили, так сразу пойдут вычисления по их подготовке. И в том числе загрузка.

Да, забавно, что Редукс тоже появился не сразу, и не за авторством Фейсбука.

Сломал мозг пытаясь понять таблички которые должны были сравнить скорость библиотек. В первой таблице по два-три числа через запятую, в других по несколько точек на число, какие-то множители посередине, строки не упорядочены по значению… :)

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

А вы пробовали работу в IE11 на практике? Не совсем понятно, как это возможно, ведь Proxy не полифиллится. Если поддержки одного Reflect достаточно, то зачем тогда там Proxy?

На самом деле Reflect был вчера выпилен. А насчет прокси
— полифил примерно для всего — github.com/tvcutsem/harmony-reflect
— полифил только для прокси — github.com/GoogleChrome/proxy-polyfill
Оба достаточно просты — используют дескрипторы для перехвата доступа к полям обьектов, что в принципе не медленно, но сильно медленнее чем прямой доступ к обьектам.

К сожалению скрипт который измеряет скорость работает не в браузере, и показать чиселки для сравнения я сейчас не могу. Но то что работает — 100%

Спасибо за ссылки. Я раньше читал страницу https://babeljs.io/learn-es2015/#proxies и там указано, что


Due to the limitations of ES5, Proxies cannot be transpiled or polyfilled.

Исходя из этого, думал что совсем нельзя.
Оказывается, Proxy таки возможно заполифиллить, но только частично


The polyfill supports just a limited number of proxy 'traps'.
В данном случае требуется только геттеры, для чего дескриптопы просто идеально подходят.

Сорри, я всё ещё не понимаю, зачем все эти движения, отдельные библиотеки, почему бы просто не считать ничего в mapStateToProps, передать сырые данные, а потом в том же рендере сделать с ними все что нужно?

Исключительно чтобы атмосферу не греть. Потому что рендер ДОРОГО! Если что-то начинает пересовываться — оно не остановиться пока в PureComponent не упрется.
Основная задача mapStateToProps — быть pure и idempotent. А если вы с этим не согласны — лучше вообще redux не использовать.
Возьмем redux-form, который будет дергать стейт на каждое нажатие клавиши. Или react-beautiful-dnd, который будет делать все тоже самое на каждое движение мышкой.
Всегда есть куча событий, которые изменяют стор, и дергают mapStateToProps, но совершенно не относятся к ВСЕМУ приложению — что-то одно маленькое должно обновиться, а все остальное — нет.
mapStateToProps и так вызовется у каждого компонента на каждый экшен, если ничего не изменилось и результат вызова mapStateToProps такой же, то редакс не будет дергать реакт, что нам и нужно. Но чем больше логики мы кладем в mapStateToProps тем медленнее наше приложение, да, можно использвать кеши, но их вызовы и проверки тоже не бесплатные
> если ничего не изменилось и результат вызова mapStateToProps такой же

Вот именно этот момент мемоизация и обеспечивает. Именно этот момент является источником проблем. Совсем чуть чуть повычисляли значения — map/filter или просто getInitialProps() какой либо вызвали — и до свидания.
так я и говорю что не нужно ничего вычислять, просто переложить данные из стейта в пропс, а вычислять потом в рендере, да, он тяжелый, но он по дизайну тяжелый, и он не будет дергаться если ничего не изменилось
Ну в рендере точно не надо. Он должен быть тупой по определению. Обычно это делается в componentWillReceiveProps, что не всегда удобно.
И да — react-memoize про который я в этой статье добавил сноску именно «там» и работает.
componentWillReceiveProps может дергаться без изменения данных, так что туда класть что-либо тяжелое не очень прикольно
Наверное надо было указать на этот момент — если у вас есть state, и вы из этого state запросили state.todos[0].id — то memoize state будет агриться на изменения только в state.todos[0].id. Одновременно с этим — если сам стейт или state.todos или state.todos[0] остались без изменения — более глубокие проверки не будут производится в принципе.
В начале производиться shallow сравнение тех частей которые могут быть «flexible», и если они не изменились — значит можно вернуть закешированный результат.
Если же они изменились — можно пойти глубже проверять. В планах есть немного передумать этот алгоритм и сильно-сильно ускорить.
Пока только есть незарелизенная автомагия beautiful-react-redux, который areStatesEqual настроить чтобы полностью и «быстро» игнорировать изменения которые не нужны, а потом уже «сравнивать» долго и упорно прошедшие.
Рендер вызовется сменой локального стейта или рендером родителя (если не ставить везде PureComponent что бывает еще медленнее или ручным sCU) вне зависимости от изменений куска данных в сторе, который нужен данному компоненту. Поэтому reselect и придумали
попробовал более магическую магию (beautiful-react-redux), время отрисовки страницы поднялось с 2.0 секунд до 2.7. Не все так радужно как кажется

Ээээ… красота требует жертв. Но можно узнать что за примерчик 2 секунды рендерится, я думал что обычно в 60 FPS, или 16 мс уложится надо.

дашборд с кучей карточек, 425 реквестов к бекенду, 1043 экшена прокидывается через редакс, 84 компонента, слушающие стейт
Возможно постоянные изменения убивают возможность что либо мемоизировать, тем самым время затраченное на сахар сильно выпирает, но тут надо смотреть конкретный пример — по тестам 160000 вызовов (1000 экшенов на 84 компонента на двойную обертку) должны занять доли секунды.
Был бы очень признателен увидеть ваш mapStateToProps. Возможно другая моя подделка — redux restate — сможет исправить ситуацию.

вот наверно самый жирный


const mapStateToProps = (state, ownProps) => {
    return {
        ...ownProps,
        tasks: state.tasks,
        news: state.news.data,
        announcements: state.announcements.data,
        loading: (!!state.tasks.loading || !state.tasks.data) || (!!state.announcements.loading || !state.announcements.data) || (!!state.news.loading || !state.news.data)
    }
};
Ок, и таких 80 чтук? А что меняется посредством 1043 ивентов? Я бы ожидал, что они на основе своих props что-то из state таскают, но вроде как нет.
Что будет если ownProps не спредить? (редакс всеравно передаст их в компонент) Поскольку они НЕ используются для доступа в стейт их вообще не надо использовать и в аргументы не просить.
В крайнем случае у connect есть mergeProps опция.

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


const mapStateToProps = (state, ownProps) => {
  return ({
    ...ownProps,
    item: state.tasks[ownProps.id]
  });
};
Я общем почти гарантирую что если убрать ...ownProps то для вас ничего не изменится (mergeProps сделает тоже самое), а в прокси прилетит сильно меньше данных.
"...ownProps" заставляет прокси думать что вам нужны все значения в ownProps, что многократно увеличивает накладные расходы и вообще не совсем правда.

спасибо, уберу ...ownProps, будет чуть быстрее… я еще посмотрел, mapStateToProps дергаются примерно 8000 раз на рендере страницы

Вообщем надо будет будет подумать о том как НЕ мемоизировать функции, которые мемоизировать не надо. Например как все ваши.

Прошла неделя, вышла новая версия — время повторить измерения.
(должно быть лучше)
Failed to compile.
Failed to minify the code from this file: 
        ./node_modules/proxy-polyfill/src/proxy.js:18 

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

Честно говоря главного в статье я и не заметил. А где описание того, как оно работает? Ну кроме того, что там Proxy. Это же самое интересное. Да и всякие шаманства вроде двойного оборачивания тоже можно было пояснить на примере, имхо ;)


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


Если я всё правильно понял, то звучит просто и сердито. Но боюсь, такой инструмент надо очень с умом использовать, не перейти грань, когда сравнений/аллокаций/прочей магии не получится сильно больше, чем самих вычислений.

Спасибо что описали как оно работает. Единственное отличие — функция должна следить за тем что она возвращает, так как если какой-то промежуточные значение, которое вообще можно проигнорировать, оказалось в результате — «ниже» этого ключа ходить не надо.
И насчет грани все правильно написали. Очень тонкая грань. Но! в рамках react/redux на самом деле можно использовать функции пожирнее без особых проблем.
Сложность — в дисциплине соблюдения иммутабельности данных состояния и чистоты композирующих функций. Но эта дисциплина полезна не только для использования этой библиотеки, она вообще помогает выдержать правильную архитектуру приложения.
  1. Расчет чисел фибоначи. Тест из библиотеки fast-memoize
  2. base line x 123.592
  3. fast-memoize x 203.342.420
  4. lodash x 25.877.616
  5. underscore x 20.518.294
  6. memoize-state x 16.834.719
  7. ramda x 1.274.908


Ну — не самый худший вариант.

Хотелось бы уточнить единицу измерения и какое значение (min/max) считается лучшим + не самый худший вариант для кого?

Единица измерения — число операций в секунду. Больше — лучше. Не самый худший вариант, очевидно, для рассматриваемой memoize-state.

После нескольких упражнений, я в конце концов отказался от React, Virtual DOM и шаблонов, в пользу реального DOM и макетов. Написал маленькую библиотеку и построение UI опять стало весёлым развлечением.
Современные браузеры уже настолько сами заоптимизированы, что не аффектят rendering если свойство реально не поменялось, и вместо того чтобы оптимизировать оптимизацию, можно просто назначать свойства и устанавливать атрибуты.
Время на скриптинг падает в несколько раз при неизменном времени на рендеринг.
Атрибуты — да, а вот эффективный рендеринг, например, списка (аналог ангулярного «item in list track by item.id») уже не будет таким тривиальным. А если на элементы ещё и события вешаются, и объекты (компоненты) инстанцируются, а в элементах ещё и вложенные списки, то вот у вас уже и получился или virtual DOM, или dirty checking, или ещё какой математический монстр, далёкий от изначальной простоты вашей библиотечки.
Представьте себе — всё то, что Вы перечислили и списки и элементы с событиями и в них ещё элементы и списки в них и тоже с событиями летает как родное (потому что родное),
а на фоне у этого всего анимированная канва с 60 fps и туча целевой арифметики с интерполяциями и элементами ИИ, тупо работает само на не самом мощном ноутбуке.

Я не говорю о том, что оно не может летать, а лишь о сложности результатирующего кода, сопоставимой со сложностью Реакта или Ангуляра. (Хоть и не очень убедительно звучат ваши слова о полётах, должен признаться :))

Ну, знаете ли, эквивалентный код на $mol куда эффективнее работает, чем на Ангуляре и Реакте. И ничего, "никто не видит проблем".

В том что реализация $mol проще и эффективней. А популярен почему-то сборник костылей на А.

Only those users with full accounts are able to leave comments. Log in, please.