Pull to refresh

Comments 23

Мы React используем с первых версий, и уже несколько десятков приложений склепали. И я в упор не понимаю весь этот хайп про оптимизацию реакта.

У меня опыт такой:
— все что влезает на экран, даже весьма сложные UI — тормозить не будет, даже если перерисовывать вообще все на каждое действие.
— если все влезает в 2-3 экрана — это оптимизируется одним-двумя shouldComponentUpdate-ами, где-то в серединке — чтобы отсечь обновления
— если у нас больше 3 экранов — всяке гриды, ленты, длинные списки — тормозит уже браузер, и это лечится всякими виртуальными скроллингами. Оптимизировать реакт в таких случая особо смысла нет.

Частая причина тормозов — отстутствие у JS-разработчиков базовых знаний про алгоритмы. Например, не привыты навыки заменять линейный поиск на lookup по хешу. Может потому что оно неудобно в JS. Могут написать алгоритм на O(N^3) на доставании данных, и потом «что-то реакт тормозит».

То, что влезает на 1 экран может содержать как 10 элементов, так и 100, так и 1000. Элементарнейший пример — excel-табличка против простого todo-списка. Да, в таких вещах используется вирт. скроллинг (иначе оно даже не загрузилось на мало-мальски среднем файле). Но даже одного экрана таблицы хватает, чтобы, при кривой реализации, выжрать всю батарейку вашего ноутбука или мобильника в краткие сроки.


Касательно асимптотики. Ну вот возьмём типичное redux+react приложение. Пусть у нас в react-древе будет всего пару сотен элементов. Мы совершили какое-то действие и store обновился. На самом верхнем уровне за счёт connect-subscribe вызывается render корневого или около-корневого react-компонента. Мы забили на здравый смысл и не используем PureComputed. Собственные shouldComponentUpdate function-ы мы не пишем тоже. Зачем? Что в итоге? render по цепочке вызывается для всех 200 элементом нашего react-древа. На любой чих. Всегда. Всегда сформировывается новое древо. И всегда сверяется со старым. В итоге в DOM улетает одно обновление, скажем, setAttribute.


А теперь у нас лимитированное кол-во connect-ов (используем с мозгами), мы используем мемоизацию для тяжёлых вычислений, не пробрасываем callback-prop-ы свежесгенерированными анонимками. Обновление store-а затрагивает несколько mapStateToProps и производится несколько shallow-сравнений. render вызывается только у одного компонента и на выходе мы получаем тот же самый setAttribute.


Разница огромная. На сложных приложениях она заметна не то, что невооружённым взглядом, а на километровом расстоянии.


Кривая реализация на авось может загубить даже простейшие приложения. Простой пример — у меня дичайше тормозило приложение при открытых devTools-ах. Просто адово. Причина? Redux-расширение. Оно с, настройками по умолчанию, сериализовывало ВЕСЬ store на любой чих туда и обратно. Соответственно оно не имело ни малейшего представления о ссылочной целостности. Съедало столько памяти сколько могло пережевать и начинало медленно помирать. Решилось всё одной единственной настройкой optoins: false, которая will handle also circular reference. С тех пор — порхает как бабочка.

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

И вы решили проблему с помощью асимптотической оптимизации, если я правильно понял.
(Патч вместо всего объекта).

Я просто поменял конфиг для redux-dev-tools-а. Там у него свой middleware подключается. И если указать options: false, то он умеет circular references. И соответственно перестаёт делать 99.999% лишней работы.

Кто сказал, что оптимизация (в т.ч. асимптотическая) всегда требует больших усилий и ухудшения читабельности кода?


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

Я затрудняюсь с точным расчётом асимптотики этой проблемы, но суть верна, да ;)

Месье, сначала бы посмотрели, как работает модуль react-redux. Открою секрет. Каждый connect() по умолчанию порождает PureComponent, поэтому не будет там лишних обновлений.

miraage, количество лишних vdom-обновлений будет зависеть от устройства конечных mapStateToProps. Но да, вы верно подметили, connect проставит часть pureComponent-ов за нас и часть лишней работы срежет.

«Хороший» способ оптимизации — ограничивать область распространения изменений. Не надо вешать на каждый чих shouldUpdate, достаточно в узловых местах добавить connect, которые будет ташить данные из стора, а не родителя, создавать как бы «автономную» область.
Ну и не стоит забывать что connect создает PureComponent, который только на изменение стора и реагирует.
Но вообще, по моему опыту, большая часть зла исходит от самого реакта и его экосистемы, чем он кривых рук конечного програмиста.

Ну если в shouldComponentUpdate пихать таких слонов как _.isEqual то можно достичь просто фантастических тормозов :)


shouldComponentUpdate(nextProps) {
  if (JSON.stringify(nextProps) !== JSON.stringify(this.props) {
    ....
  }
}

этот пример оттуда просто шикарен :)

О том и речь. Не все библиотеки одинаково хороши. В некоторых такие ужасы написаны, которые себе в продакшен тащить не хочется.

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


Типовой компонент обязан перерендериться при изменении любого из своих свойств.

UFO just landed and posted this here

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


Apparently, most of my components changed most of the time, so on the whole, my app got slower. Oops.

что его компоненты настолько часто изменялись, что shallow-проверки в shouldComponentUpdate оказались overhead-ом, который перекрывает пользу от отсутствия построения лишнего virtualDOM-а.


Мой ещё не богатый опыт был строго противоположным. Проверки в shouldComponentUpdate ускоряли приложение на порядок (раз в 10), что было хорошо видно на fire-графиках.


Типичный набор props состоит из 3-7 полей. В итоге типичная shallow-проверка это 3-7 ===. Как это может быть настолько медленным, чтобы не оправдать избавление от лишнего render-а и последующего сравнения двух vdom-деревьев? render — это множественные аллокации и присвоения. Сравнение двух деревьев это куда большее количество ===, чем в shallow-проверке.


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


Касательно преждевременной оптимизации. Где он её тут увидел? Разница только в нотации method(){} против method = (){}. 3 символа?
Ну а аргумент про то, что callback вынесен далеко от render-метода, и заставляет бегать по файлу… Ну тут на вкус и цвет товарищей нет. Лично я предпочитаю держать render-методы как можно более мелкими, простыми и очевидными. А это значит я не нагромождаю их вычислениями, callback-ми, сложными условиями и пр… Мне кажется, в идеале, render метод должен быть куском html-like кода.

У меня тоже «пригорело». Какие-то «вредные советы» сплошные. Единоразовое создание двух замыканий в конструкторе это у него почему-то overhead, а создание этих же замыканий при каждом рендере (многократно) это нормально.
На практике такого не случится никогда. Ни-ког-да. Если вы не ставили производительность во главу угла с самого начала, то неэффективные решения будут появляться повсюду, заражая каждый квадратный килобайт кода в вашем проекте.

Хоть в рамочку вешай.

Статья с намеренно неправильной аналогией в самом начале — это грязный демагогический приём.


Вдобавок к вышесказанному товарищами faiwer и andy128k: уж если автор советует пихать лямбды в jsx (в то время как все автоматически избегают создания лишних замыканий где угодно, не говоря уж про циклы и часто вызываемые методы, такие как render), то боюсь представить, какова будет его реакция на плагин react-constant-elements, который идёт ещё дальше, и хоистит jsx-элементы.


P.S. А точнее он начал статью с фотографии своей новой спальни. Спальня красивая, мне понравилась. Но это не спасло статью.

Есть подход с мемоизацией.
https://github.com/timkendrick/memoize-weak
https://github.com/timkendrick/memoize-bind


Вторая как раз про реакт. А т.к. объект функции будет тем же самым, то souldComponentUpdate корректно отработает.
Плюс там используется weak-map, по этому не будет проблем с мусором.

Слишком сложно как-то написано… На утечки памяти это чудо вообще проверялось?
Я знаю про этот класс, но я его не сразу нашел…
Sign up to leave a comment.