Comments 52
Memoize-state
в mapStateToProps
.Не очень понятно с
beautiful-react-redux
— что значит:молча обернет mapStateToProps два раза в memoize-state… (до свидания re-reselect)
Хотелось бы более подробной информации.
За библиотеку спасибо
Если у вас есть два инстанса компонента, то в начале первый что-то возьмет из state на основе своих props, а потом второй, а потом опять первый. И всегда кеш будет чистый, так как то что там храниться — «не подходит».
Полуофициальное решение проблемы — re-reselect, который позволяет указать как «разделять» компоненты.
Второе полуофициальное решение — завернуть createSelector в замыкание, так чтобы проблемы с кешом не будет. Но тогда они не смогут «шарить» кеш между инстансами.
beautiful-react-redux оборачивает mapStateTopProps в memoize-state «снаружи», и еще раз «внутри». Те для каждого отдельного элемента, и для всех целиком, на случай если разницы между ними нет.
В общем универсальное решение.
- Необходимость писать много селекторов используя reselect дает неплохую инкапсуляцию — каждый контейнер имеет свои специфические селекторы, а другие просто их используют, ничего не зная о реальных путях в state. Что рекомендуется в случае Memoize-state?
- Proxy создается на каждый вызов mapStateToProps?
2. И да и нет. Для того чтобы возможная «другая» мемоизация работала требуется предоставлять «одинаковые» обьекты завернутые в «однаковые» прокси. В общем там внутри все созданные прокси храняться в WeakMap, и без надобности не создаются.
Почему нет — потому что сам state между вызовами будет разный, и для него прокси будет создаваться каждый раз.
Как говорилось выше — без проблем пару миллионов в секунду.
Забавно, как React с помощью обвесов превращается в дедушку Knockout. Забава в том, что изначально pull-концепция (вытягивания данных всеми ветками из корня модели) была выбрана как более простая против push-концепции (проталкивание данных из корня в ветки, которые задеты изменениями), вместо запутанных и многословных observables (см. Knockout-mapping). Мол, у нас виртуальный DOM, он сам отметёт то, что не нужно менять, не заморачивайтесь (640 килобайт хватит всем). Создатель Vue.js в этом смысле оказался чуть прозорливее.
В итоге и получается, что и коробки Vue, да и Angular, могут работать сильно быстрее. Ну просто потому что програмисты такие програмисты. Глаз за глаз за ними нужен. Ну или костыль. И memoize-state – фабрика костылей. Подопрет где нужно и все окей.
Изначально концепция редукса — именно push. Селекторы просто вырезают из огромного стейта кусочек. Собственно это та же концепция, что и у RxJS — у вас есть стрим глобального состояния. И есть зависимые стримы, получаемые маппингом и фильтрацией оригинального. И пока кто-то не запушил данные, вы не можете их отфильтровать. А вот концепция knockout/mobx/vue как раз pull — пока вы данные не запросили, никто их и готовить не станет, а как запросили, так сразу пойдут вычисления по их подготовке. И в том числе загрузка.
Сломал мозг пытаясь понять таблички которые должны были сравнить скорость библиотек. В первой таблице по два-три числа через запятую, в других по несколько точек на число, какие-то множители посередине, строки не упорядочены по значению… :)
А вы пробовали работу в IE11 на практике? Не совсем понятно, как это возможно, ведь Proxy не полифиллится. Если поддержки одного Reflect достаточно, то зачем тогда там Proxy?
— полифил примерно для всего — 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, передать сырые данные, а потом в том же рендере сделать с ними все что нужно?
Основная задача mapStateToProps — быть pure и idempotent. А если вы с этим не согласны — лучше вообще redux не использовать.
Так рендер не вызовется, пока данные не изменятся
Всегда есть куча событий, которые изменяют стор, и дергают mapStateToProps, но совершенно не относятся к ВСЕМУ приложению — что-то одно маленькое должно обновиться, а все остальное — нет.
Вот именно этот момент мемоизация и обеспечивает. Именно этот момент является источником проблем. Совсем чуть чуть повычисляли значения — map/filter или просто getInitialProps() какой либо вызвали — и до свидания.
И да — react-memoize про который я в этой статье добавил сноску именно «там» и работает.
В начале производиться shallow сравнение тех частей которые могут быть «flexible», и если они не изменились — значит можно вернуть закешированный результат.
Если же они изменились — можно пойти глубже проверять. В планах есть немного передумать этот алгоритм и сильно-сильно ускорить.
Пока только есть незарелизенная автомагия beautiful-react-redux, который areStatesEqual настроить чтобы полностью и «быстро» игнорировать изменения которые не нужны, а потом уже «сравнивать» долго и упорно прошедшие.
Ээээ… красота требует жертв. Но можно узнать что за примерчик 2 секунды рендерится, я думал что обычно в 60 FPS, или 16 мс уложится надо.
Был бы очень признателен увидеть ваш 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)
}
};
Что будет если ownProps не спредить? (редакс всеравно передаст их в компонент) Поскольку они НЕ используются для доступа в стейт их вообще не надо использовать и в аргументы не просить.
В крайнем случае у connect есть mergeProps опция.
с ownProps не заморачивался, есть — есть, нет — пустой объект будет, что сильно не мешает.
есть вариант, когда из стейта более конкретные данные вытягиваются
const mapStateToProps = (state, ownProps) => {
return ({
...ownProps,
item: state.tasks[ownProps.id]
});
};
"...ownProps" заставляет прокси думать что вам нужны все значения в ownProps, что многократно увеличивает накладные расходы и вообще не совсем правда.
спасибо, уберу ...ownProps, будет чуть быстрее… я еще посмотрел, mapStateToProps дергаются примерно 8000 раз на рендере страницы
Вообщем надо будет будет подумать о том как НЕ мемоизировать функции, которые мемоизировать не надо. Например как все ваши.
(должно быть лучше)
Честно говоря главного в статье я и не заметил. А где описание того, как оно работает? Ну кроме того, что там Proxy. Это же самое интересное. Да и всякие шаманства вроде двойного оборачивания тоже можно было пояснить на примере, имхо ;)
Оно детектирует все поля к которым происходит обновление, складирует пути в некое древо путей, а потом при последующем просчёте пробегается по этому древу, по его ветвям, вплоть до листьев, в поисках единственного отличия? И если ничего не найдено, то возвращает кеш? Как-то так работает? Дескать если какая-то промежуточная коллекция не изменилась, то и листья не изменились, и можно вглубь не копать, а если таки изменилась, то возможно не изменилось то, что нас интересовало, и можно пойти вглубь и проверить наверняка, т.к. функция чистая?
Если я всё правильно понял, то звучит просто и сердито. Но боюсь, такой инструмент надо очень с умом использовать, не перейти грань, когда сравнений/аллокаций/прочей магии не получится сильно больше, чем самих вычислений.
И насчет грани все правильно написали. Очень тонкая грань. Но! в рамках react/redux на самом деле можно использовать функции пожирнее без особых проблем.
- Расчет чисел фибоначи. Тест из библиотеки fast-memoize
- base line x 123.592
- fast-memoize x 203.342.420
- lodash x 25.877.616
- underscore x 20.518.294
- memoize-state x 16.834.719
- ramda x 1.274.908
Ну — не самый худший вариант.
Хотелось бы уточнить единицу измерения и какое значение (min/max) считается лучшим + не самый худший вариант для кого?
Современные браузеры уже настолько сами заоптимизированы, что не аффектят rendering если свойство реально не поменялось, и вместо того чтобы оптимизировать оптимизацию, можно просто назначать свойства и устанавливать атрибуты.
Время на скриптинг падает в несколько раз при неизменном времени на рендеринг.
а на фоне у этого всего анимированная канва с 60 fps и туча целевой арифметики с интерполяциями и элементами ИИ, тупо работает само на не самом мощном ноутбуке.
Я не говорю о том, что оно не может летать, а лишь о сложности результатирующего кода, сопоставимой со сложностью Реакта или Ангуляра. (Хоть и не очень убедительно звучат ваши слова о полётах, должен признаться :))
Как я написал самую быструю функцию мемоизации