Pull to refresh

Comments 99

А можно пояснить, как это работает?


new Vue({
    el: '#app',
    store
})

Как так store без ключа?

UFO just landed and posted this here

Зачем и у чего нужно менять прототип в данном примере?

Интересно, в чем смысл называть dispatch commit'ом, а middleware mutation'ами. И почему тогда store не какой-нибудь vault...

[сарказм]Evan You любит постоянно создавать что то "новое".[/сарказм]
А если честно, вуе становиться похожим на винегрет из различных практик в немножко самобытной обертке. Кому то нравится, но а меня смущает заворачивание методов в объекты, имеющие какой то магический клей между друг дружкой. Например, проследить вложенность методов в IDE становиться затруднительно, потому что IDE просто не может понять связи между ними. Но это имхо.

Redux тоже недружелюбен для IDE. Проследить цепочку Component -> ActionCreator -> Action -> Middleware -> Reducer -> Connect -> Component очень сложно. Все вызовы непрямые, все объекты предварительно биндятся/обёртываются специальными функциями. Обращение ко всему через объект Props ломает оставшиеся подсказки IDE.


Спасает только то, что структура папок/файлов в проектах на React примерно одинаковая везде, и ясно, куда смотреть, если нужно подебажиться.
P.S. — пишу на React 2 года. Юзаю Redux в своих проектах

Если о реакт-компонентах, то там все достаточно дружелюбно с IDE. А при диспетчеризации да, к сожалению, все достаточно не очевидно.

IDE нормально подсказывает если PropTypes прописывать

Редакс ничего не знает про компоненты. Что там ИДЕ подскажет?
Потому TypeScript сильно выручает
фишка redux в том, что можно просто сделать grep по названию action creator и action, и уже таким образом проследить цепочку. естественно не настолько удобно как в ide, но с задачей справляется весьма неплохо.

А какой смысл называть mutation reducer'ом?

1) Ну первым придумали reducer
2) Reducer более наглядно чем mutation.
Reducer от reduce — он принимает очередь из action'ов и выдает объект
Можно было бы сказать что Mutation мутирует, но он не делает этого, ведь в коде вообще нету генов

Возможно я не правильно понял это в статье (не работал с Vue), но:


  • Vue "мутирует" свои переменные/объекты/данные. Т.к. изменяет их. "Мутабельность". Обратите внимание, что mutations.ADD_NOTE ничего не возвращает.
  • Redux прогоняет всё через цепочку reducer-ов (.reduce) получая на выходе новый объект.

Поправьте меня, если я не прав.

Все просто, мутации императивны, редьюсеры функциональны. Со всеми вытекающими.

Оу, тогда я резко против этих mutations'ов, это же Глобальный Объект и в большом приложении умрешь выяснять какая из мутаций гадит в него

Если честно я достаточно поверхностно знаком с redux, но не совсем понимаю в чем иммутабельные объекты, в данном случае, выигрывают. Если говорить про дебаг, то можно логгировать каждое действие, делать снапшот состояния тоже можно, с чем vue-devtools неплохо справляется. В чем поиск мутаций отличается от поиска редьюсеров?>

Иммутабельность гарантирует вам идентиченость всего состояния независимо от времени для определенного набора экшенов. Из этого вытекает целый ворох всяких бонусов из серии тестируемости, отладки, time-travelling, оптимизаций рендеринга и прочего.
Сделать 2 снепшота, конечно, можно, пройдясь по ним вглубь, но это дорого. И, потом, как вы определите, различаются ли они, если кто-то сбоку что-нибудь изменил? Только опять пройдясь по ним вглубь, что, опять же, дорого.
В случае же с неизменяемыми структурами вы просто получаете другой объект, если он действительно изменился, и делаете сравнение по ссылке, что дешево.

Поиск мутаций отличается от «поиска редьюсеров» тем, что вы не ищите редьюсер, который «что-то меняет». Редьюсер каждый раз в виде реакции на декларативный экшен возвращает вам либо старый объект, либо новый. Ну, то есть, искать его не нужно, весь ваш стейт строится на композиции этих редьюсеров, тогда как мутации могут что-то изменить глубоко в структуре, а на верхнем уровне вы об этом «на халяву» не узнаете.

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

"На халяву" узнаем только о факте изменения, а для выявления того, что действительно изменилось, то же глубокое сравнение. Может без необходимости заходить во все ветви, но глубокое. И что значит "искать не нужно"? Чтобы убрать или изменить какую-то "мутацию" нужно бродить по всему дереву композиции, чтобы понять что где меняется.

А зачем вам выявлять, что действительно изменилось? Редьюсеры пришли из ФП, там все строится на композиции, сильно сдобренной мемоизацией. Если вы получаете измененный стейт из двух подобъектов, вы заново прогоняете редьюсеры для этих подобъектов, и, каждый из низ, если пришел не интересующий его экшен, вернет тот же подобъект (тот самый дефолтный кейс в свитче). Таким образом, вы не «ищите» изменения для последующей сборки нового стейта, вы сразу собираете новый.

Идем дальше, редьюсеры в виде redux писались для реакта, а там virtual dom и трюк с shouldComponentUpdate. Если в компонент попадает старый объект из стейта он просто не будет перерендериваться (это своего рода мемоизация), и вот как раз тут неизменяемая структура гарантирует, что если посередине стейта что-то изменилось, то все ссылки выше вплоть до корня тоже изменились, тогда как при мутациях это еще нужно будет проверить. Таким образом, вам опять не нужен поиск, вы просто заново строите дерево компонентов.

Стоит еще упомянуть reselect с его мемоизирующими селекторами для redux connect, там опять таки идет упор на неизменяемость, ведь, хоть там и одноуровневый кэш, проверка на изменение значения между селекторами при их композиции проходит так же по ссылке на конкретный объект без поиска изменений.

И самое интересное, чтобы убрать изменение, вам достаточно просто где-то хранить ссылки на стейты целиком, а дальше реакт и реселект сделают всю грязную работу по выявлению мест для обновления.
Я прошу прощения, ерунду ж написал:
Если вы получаете измененный стейт из двух подобъектов,
Не измененный стейт, а, если вам приходит какой-то экшен в корневой редьюсер, то он сначала пробрасывается во все дочерние редьюсеры по всему дереву композиции, ну а дальше, если хотя бы один из них вернул новый объект, наверх опять же уйдет новый объект и так далее до самого корня.
UFO just landed and posted this here

С другой стороны медали:


  • мутабельный код писать много-много проще, и этот код разительно понятнее/очевиднее
  • observer-ы позволяют не делать вообще никаких сравнений, а сразу по месту обновлять DOM и что-угодно ещё (что подписалось на нужные observable-значения)

Другой подход ― другие преимущества, другие недостатки.

А зачем вам выявлять, что действительно изменилось?

Для отладки, как минимум. Для изменения этих изменений.

Для изменения этих изменений.
Не очень понял, что вы имеете в виду. Можете привести пример?

Задача на изменение логики в незнакомом коде. Да или даже исправление бага.

Тогда я не понимаю, какое отношение к этому имеют изменения в данных?

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

Все-равно как-то сухо получилось.
Вот у вас есть структура данных, и вам нужно узнать где в коде устанавливается конкретное значение в ней. Ну вы ищите это в самом коде, разве нет? Как вам поможет в этом изменяемость/неизменяемость этой структуры?
Если вы про то, что нужно узнать, на какие участки этой структуры повлияет конкретный экшен, то вы делаете поиск по нему в коде редьюсеров.
Либо я все-таки не понимаю, о чем вы.

Об отладке в браузере, где возможности поиска в коде ограничены.

Ладно, не хотите пример, и не надо.
Вам неизменяемость как-то мешает отлаживаться в браузере? Может, перестают брейкпоинты работать?
Может, перестают брейкпоинты работать?

offtop. У меня одного последние 3-4 версии Chrome дичайшим образом глючат с brakepoint-ми? Особенно если это <2 webpack? Стал отлаживать в Chromium-е из-за этого.

да — breakpoint-ы стали глючить, когда Chrome сделал скачек в поддержке ES6 с 60%+ до 90%+
с тех пор от версии к версии их по-разному колбасит

Вот вы говорите про то, что дорого при отладке и тестировании делать снапшоты и сравнивать их, но на сколько дорого генерировать новые объекты на каждое действие в бою? Мы все же говорим про JS, и все это состояние вычисляется далеко не ленивым способом. По поводу изменений с боку, опять же со стороны языка толком ограничений нет, можно заморозить объекты или как-то извратится, но гарантий это все равно не даст, тем не менее при попытке изменить состояние во vuex выскочит предупреждение (или даже исключение), как с этим в redux я не знаю, где-то же конечные данные вылазят внаружу и теоритически их можно менять. Во vuex все обвернуто, чтобы можно было повесить слушателей и контролировать изменения.


Оптимизацию рендера, я при всем желании видеть иначе, вижу ее как необходимое следствие, при использовании неизменяемых структур. Поясню. Добавляя элемент в список, мы меняем весь список, отсюда следует что при рендере он должен быть обработан снова, что бы дважды не обрабатывать его элементы, мы начинаем все неистова кешировать и проверять, а это не бесплатно, и так не только со списками.


Что будет дороже в конечном счете для меня большой вопрос, на самом деле не обладаю должной компетентностью, что бы максимально объективно оценить тот или иной подход по всем критериям. Но то что вы привели это не очень очевидные плюсы подобного подхода, в контексте JS.


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

Если мы говорим про vuex и redux, то почему это другая история? Во vuex все построено на прослушке изменений конкретных участков всего состояния.

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

но на сколько дорого генерировать новые объекты на каждое действие в бою
Все не так грустно, immutable.js использует hash map trees и vector trees, чтобы не копировать структуры постоянно — вполне эффективный подход.

По поводу изменений с боку, опять же со стороны языка толком ограничений нет, можно заморозить объекты или как-то извратится, но гарантий это все равно не даст, тем не менее при попытке изменить состояние во vuex выскочит предупреждение (или даже исключение), как с этим в redux я не знаю, где-то же конечные данные вылазят внаружу и теоритически их можно менять.
Я имел в виду не ситуации, когда программист сам себе дурак и делает то, что паттерн ему запрещает, а именно redux запрещает мутации стейта, но лишь на уровне соглашений. Ну т.е. вы можете undefined переопределить, но вы же этого не делаете. А имелись в виду ситуации, когда мутация намеренно меняет кусок структуры и нужен целый ворох оберток и событий, чтобы как-то на нее среагировать. Я не говорю, что этот подход плох, он просто другой.

как с этим в redux я не знаю, где-то же конечные данные вылазят внаружу и теоритически их можно менять.
Если нужно, вы можете защитить их с помощью либо typescript и readonly/Readonly<>, либо с помощью того же immutable.js. Просто защита данных от мутаций не входит в обязанности redux, и вот тут как раз мне не очень понятно, зачем этим занимается vuex (так как вы сказали, что полетят исключения).

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

Если мы говорим про vuex и redux, то почему это другая история?
Потому как разговор уйдет в русло сравнения реализации двух разных подходов в двух разных библиотеках.
и это практически бесплатная операция

Любая операция будучи выполненной в большом цикле неприятно вас удивит. А если сам цикл выполняет в цикле… Чума. А если это всё ещё заторможено каким-нибудь .freeze или immutable.js… Хотя, я полагаю, там на production-е можно этого избежать.


Одна из неприятных вещей в том же React+Redux, заключается в том, что если поместить в state какой-нибудь часто-меняющийся параметр, расположенный глубоко в иерархии, то на любой чих придётся регулярно менять все обёртки (благо хоть не руками). И если часть из таких обёрток не самые мелкие массивы… В общем грустно всё это.


Вспомнился один из распространённых в redux хаков — держать в сторе большие массивы последовательностей и массивы значений друг от друга отдельно.

Кстати говоря, я не проверял, но очень интересно. А у вас вроде с redux-react большой опыт. Что будет если я на 7-ом уровне вложенности что-нибудь поменяю? Это заставит перестроить virtualDOM для всех вышестоящих компонентов (ссылки то обновились)? На сколько я понимаю как это работает, то должно. Благо что все соседи будут на уровне pureComponents отсеяны. Но всё равно overhead такой приличный. Я букву в input заменил а тут 100 проверок.

Я полагаю, что существуют паттерны для решения данного вопроса, например линзы. Redux вроде как не накладывает ограничения на то, что именно должно хранится в стейте. У меня с ФП и redux в частности опыта особо нет, не могу сказать на сколько это повсеместно используется, но использование выглядит логичным для решения проблем, которые были вами озвучены. А так тоже интересно было бы узнать как это решается на практике.

Внимательно изучил статью по вашей ссылке. По сути линзы это удобные функциональные обёртки по установке и получению значения в иммутабельной структуре. Но никаких проблем с производительностью они даже близко не решают. То, что паттеры для быстрой работы с иммутабельными списками быть должны, я и не сомневаюсь. Вот только не уверен, что такое есть и хорошо работает в JS. У нас тут даже типизированные массивы относительно недавно появились. Тут хорошо бы специалиста в тред :)

И ещё 1 вопрос. Есть у меня массив на 200 элементов. Нужно обновить элемент под номером 143. [...arr.slice(0, 143), el, ...arr.slice(144)] выглядит жутковато, но ещё хуже, оно же заставляет ЦП и ОЗУ гонять туда сюда одно и тоже во имя бога иммутабельности. Кажется такие вещи пока никем и никак не оптимизируются (в рамках JS, про Haskel и др. функ. языки молчу). Я прав? Или есть какие-то простые и эффективные решения?

Не в тему, но булк операции можно делать так...


var list1 = Immutable.List.of(1,2,3);
var list2 = list1.withMutations(function (list) {
  list.push(4).push(5).push(6);
});
assert(list1.size === 3);
assert(list2.size === 6);

Выше сказали, что под копотом иммутбл оптимизирует работу с данными, поэтому сомневаюсь, что в реальности оно делается именно так, как вы описали.

Во внутренностях Immutable.js не копался, не знаю. Но графики отсюда сильно расстроили. А учитывая, какой там синтаксис, да и вес в 56 KiB minified…

Спасибо за ссылку, там отыскалась seamless-immutable, одна из самых быстрых. Все стало еще неоднозначней… с одной стороны да, Immutable.js работает долго, с другой стороны есть альтернативы, которые работают быстро.


P.S.: Здесь хорошо подметили: странно почему vuex стор контролирует или пытается контролировать иммутабельность, а не отдельная специальизированная библиотека.

Считаю что нужно уточнить некоторый момент: vuex пытается контролировать мутабельность, чтобы злой буратино случайно не изменил что-то со стороны. И все завязано на структуре данных, потому что vue переопределяет все свойства объектов с помощью Object.defineProperty, у массивов и прочих структур методы которые влекут мутацию (например push, pop) тоже переопределяются если я не ошибаюсь. На этом завязана вся реактивность vue и возможность контроля вытекает из этого же.


https://github.com/vuejs/vuex/blob/dev/src/index.js#L390


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

Любая операция будучи выполненной в большом цикле неприятно вас удивит.
Все же, достаточно абстрактно звучит. Проверки выполняются в фазе рендеринга в virtual dom, а они ограничены requestAnimationFrame (по крайней мере в реакте), ну и проверка === все-таки легче всего остального возможного.

Одна из неприятных вещей в том же React+Redux, заключается в том, что если поместить в state какой-нибудь часто-меняющийся параметр, расположенный глубоко в иерархии, то на любой чих придётся регулярно менять все обёртки (благо хоть не руками).
Вспомнился один из распространённых в redux хаков — держать в сторе большие массивы последовательностей и массивы значений друг от друга отдельно.
Одна из рекомендаций по redux — это держать все в нормализованном в виде, а не в глубоких структурах, как раз для того чтобы избежать изменения родителя, если у него в списке изменился кто-то. Ну т.е. это не хак, это фича.
Что будет если я на 7-ом уровне вложенности что-нибудь поменяю? Это заставит перестроить virtualDOM для всех вышестоящих компонентов (ссылки то обновились)? На сколько я понимаю как это работает, то должно. Благо что все соседи будут на уровне pureComponents отсеяны. Но всё равно overhead такой приличный. Я букву в input заменил а тут 100 проверок.
Тут надо подумать, а действительно ли вам нужно значение из локального инпута в глобальном стейте? Redux не обязывает вас держать в глобальном стейте абсолютно все. Если же вам все-таки нужно это значение, ну, например, выводить синхронно куда-то в другое место, то вы можете то место обернуть в контейнер с селектором именно этого значения, а в вышестоящих не использовать этот селектор. То же самое касается и родителей этого инпута, если им самим это значение из стейта не нужно, то и доставать его тоже не нужно. Соответственно, перерендериваться ничего не будет.

И ещё 1 вопрос. Есть у меня массив на 200 элементов. Нужно обновить элемент под номером 143. [...arr.slice(0, 143), el, ...arr.slice(144)] выглядит жутковато, но ещё хуже, оно же заставляет ЦП и ОЗУ гонять туда сюда одно и тоже во имя бога иммутабельности. Кажется такие вещи пока никем и никак не оптимизируются (в рамках JS, про Haskel и др. функ. языки молчу). Я прав? Или есть какие-то простые и эффективные решения?
Именно поэтому рекомендуется данные держать в нормализованном виде для простоты их изменения, а в самих списках — айдишники этих элементов. Тогда при изменении «объекта в списке» вы меняете сам объект в хэшмэпе по его id, а сам список не трогаете.

оно же заставляет ЦП и ОЗУ гонять туда сюда одно и тоже во имя бога иммутабельности
Справедливости ради, если у вас в массиве объекты, то копируются только ссылки на них, а не вся структура.
Все же, достаточно абстрактно звучит

Давайте представим, что программист ударился головой об угол и решил, что mouseover координаты хорошо бы держать в store. В случае vue, knockout это будет достаточно лёгкой операцией, которая почти ничего не задействует. 1 observable, 1+ subscriber. Хотя конечно руками напрямую будет ещё быстрее.


В случае React+Redux будет через чур много телодвижений. Страдает и производительность и энерго-эффективность (ноутбуки, мобильники, планшеты).


Пример очень гипертрофированный, понятно, что ряд вещей должны работать в обход фреймворка, даже если можно реализовать это и с ним. Но по нему хорошо видно, что необходимость всегда всё просматривать с самого верху, вынуждает делать много-много-много лишних сравнений и аллокаций (virtualdom мы shallow-строим заного на любой сдвиг мыши).


в контейнер с селектором

Звучит как магия какая-то. Селекторы позволяют избежать обновления ссылок на вышестоящие объекты в иерархии? Хм, я думал это просто такая мемоизация, против тяжёлых вычислений. Всё никак не доберусь до них, чтобы изучить их детальнее.


Ну т.е. это не хак, это фича.

В какой-то степени да. Но в мутабельном случае куда проще: arr.replace(oldV, newV). И никаких забот. И нет, весь список при этом обновляться не должен. Во всяком случае knockout умеет избегать этого.

Давайте представим, что программист ударился головой об угол и решил, что mouseover координаты хорошо бы держать в store. В случае vue, knockout это будет достаточно лёгкой операцией, которая почти ничего не задействует. 1 observable, 1+ subscriber. Хотя конечно руками напрямую будет ещё быстрее.

В случае React+Redux будет через чур много телодвижений. Страдает и производительность и энерго-эффективность (ноутбуки, мобильники, планшеты).
Ну вы кладете в стейт отдельный объект под это дело, и те контейнеры, которым эти координаты нужны, через селектор их достают. Те, которым не нужны, не достают и не перерендериваются.

много-много-много лишних сравнений и аллокаций (virtualdom мы shallow-строим заного на любой сдвиг мыши).
В большинстве случаев сравнивать необходимо только в контейнерах, а обычно их не очень много, и в соседних редьюсерах на том же уровне, что и изменяемое значение. Ну, селекторы еще, но они хорошо мемоизируются.

Селекторы позволяют избежать обновления ссылок на вышестоящие объекты в иерархии?
Нет, селектор позволяет выбрать из стейта то, что действительно нужно для компонента. Таким образом, если ему координаты мыши не нужны, то он и перерендериваться не будет, так как в селекторе они не достаются.

Но в мутабельном случае куда проще: arr.replace(oldV, newV). И никаких забот. И нет, весь список при этом обновляться не должен. Во всяком случае knockout умеет избегать этого.
А я же и не говорю, что проще, а только, что эта проблема успешно решается. Да, с небольшим оверхедом в виде определенной структуры данных. Кстати, что-то я не припомню метода replace ;)

Если от этого списка зависит другой список (а как правило это так. частый случай — фильтрация), то он неизбежно будет обновлён. Так что эта оптимизация в КО, фактически бесполезна. Да и обычно не это место является бутылочным горлышком. Основные узкие места — DOM и GC. На GC плохо влияет большое число долгоживущих объектов (создать и тут же уничтожить объект — сейчас почти ничего не стоит). На DOM — объём выводимых данных и количество изменений в "приаттаченных" узлах (пока не добавили в документ, узлы можно очень дёшево изменять). Реакт позволяет по минимуму трогать DOM, но если скажешь ему отрендерить 10000 элементов, он послушно это и сделает. повесив вкладку на пару секунд.

Не понял причём тут фильтрация. Я говорил про замену одного элемента в массиве (даже про .replace упомянул). Не понял пассажа про DOM, лишних изменений в DOM вроде бы ни одна из реактивных библиотек не вносит. Известных мне подходов 2: большая и тяжёлая обвязка вокруг каждого изменяемого значения (или одна на группу), либо множество сравнений для поиска отличий.

При том, что изменяем мы один список, а выводим его производный:


const tasks_filtered = ko.computed( ()=> tasks_all().filter( filter() ) )

И хоть tasks_all мы соптимизировали до push вместо concat. tasks_all неизбежно будет сгенерирован заново.А если ещё и учесть, что списки обычно не в рантайме меняются, а в каком-либо персистентном хранилище (сервер, локальная база, ссылка и тд), то и tasks_all соптимизировать толком не получится.


Наоборот, лишние изменения в DOM вносит почти почти любая библиотека. Например, выводите вы список на 100 элементов, а в видимую область помещаются только 20. Зачем обновлять те, что не видны? Это самые настоящие лишние обращения к DOM.

Вы обсуждаете явно что-то другое, а не то, что имею ввиду я. Впрочем, как обычно. Забудьте уже про ваш filter, я про него и не заикался. У меня куда чаще стоит задача изменения одного элемента в списке, нежели изменение самого списка. И реактивная библиотека на основе observable может быть устроена таким образом, что подписаться в ней для массива можно на изменение конкретных записей, а не на весь список. А DOM-binding-и просто обязаны такой инструмент использовать. Правда старая реализация в Knockout-е мне не понравилась, т.к. она всё равно делала многочисленные сравнения, вместо точечных замен. И я не в курсе, поменяли ли они это поведение или нет.


А ну и ваш virtual scrolling уже и правда всех достал (как и глупые benchmark-и на его основе). То, что вы внесли его в свою реактивную библиотеку ещё не означает:


  • что его нельзя подключить в другие;
  • что это вообще обязательная задача для реактивной-библиотеки;
  • что о нём нужно упоминать повсеместно;

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


Судя по бенчмаркам, KO неплохо так ускорили.


И вас не затруднит объяснить, чего это virtual-dom вас не достал, а lazy-rendering достал? Обе технологии делают одну и то же задачу — уменьшают число лишней работы, которую можно не делать. Только если первая делает это лишь за счёт минимизации создания новых узлов путём повторного использования уже существующих, то вторая в дополнение к этому, позволяет минимизировать число этих узлов. Более того, за счёт реактивности минимизируется и число загружаемых и подготавливаемых данных, что тоже даёт плюс к отзывчивости приложения.


что его нельзя подключить в другие;

Прикрутить-то можно что угодно, к чему угодно. Вопрос в цене этого прикручивания.


Вы ошибаетесь, полагая, что в моих бенчмарках сравниваются фреймворки сами по себе. При измерении попугайчиков тот же реакт даст куда более высокую скорость за счёт меньшего числа операций и полного обновления данных (кстати, обратите внимание как ощутимо тупят на последнем тесте "честные трудяги"). Но реальное приложение на реакте будет, наоборот, медленнее, так как будет производить лишние действия. А чтобы не производило, нужно сидеть с напильником и прикручивать костыли. Пользователю плевать сколько вы можете рендерить элементов в секунду — ему важно, чтобы страницы открывались быстро и не тупили.


Ну вот, недавно Тинькофф пиарился, что перевёл сайт на реакт:


https://www.tinkoff.ru/invest/news/ — 4 секунды (9 на мобилке) только лишь рендеринга, чтобы пользователь увидел два простых меню, короткую новость с парой картинок и криво запиленный ленивый список новостей.


Для сравнения:


http://mol.js.org/app/habhub/четверть секунды рендеринга на компе (секунда на мобилке) и вы уже можете читать ленту из огромных статей. И для этого не потребовалось ничего "прикручивать", а значит даже джуниор быстро сделает отзывчивое приложение, с чем не справилась команда профессионалов из известного банка.


что это вообще обязательная задача для реактивной-библиотеки;

Это, разумеется, задача библиотеки рендеринга, но она может быть реализована по двум противоположным схемам:


  • push, как React, где сначала ты готовишь все данные, а библиотека их отображает. Тут ленивость если и возможна, то только ручками — следим за вьюшкой, запрашиваем данные из модели. И это довольно сложно, когда содержимое страницы — не простой список с фиксированной высотой колонок, а что-то комплексное.


  • pull, как в $mol_view, где сначала ты говоришь, что ты хочешь отобразить и откуда брать данные, а вьюшка уже решает какие данные и когда ей нужны. Это позволяет практически не думать о ленивости, но при том её получать.

что о нём нужно упоминать повсеместно;

То есть о бестолковых virtual-dom, streams и flux можно на каждом углу упоминать, а о действительно крутой фиче, гарантирующей хороший уровень отзывчивости независимо от сложности приложения — нет? Почему?

И вас не затруднит объяснить, чего это virtual-dom вас не достал, а lazy-rendering достал?

Дело лично в вас. Ничего против lazy-rendering я не имею. И имею опыт в написании своего.


а о действительно крутой фиче, гарантирующей хороший уровень отзывчивости независимо от сложности приложения — нет? Почему?

Ну если вы перестанете лезть в каждый 1-й JS топик с $mol и lazy-rendering-ом, а вместо этого напишете детальную статью, где сравните сущ-ие библиотеки/плагины для известных реактивных либ, с тем как это реализовано у вас, и каковы результаты, от этого будет куда больше толку.


Насколько я понимаю, никто не мешает написать на React компоненту, в которой вся чёрная магия по lazy-loading-у уже будет сделана, и вам останется только написать что-то вроде <LazyList list={list} component={component}/> и оно полетит.


При условии, конечно, что модель вирт. скролла подойдёт к данной модели. Т.к. тот вирт. скролл, который потребовалось написать мне в прошлом году, едва ли где-то ещё можно встретить в виде готового пакета\плагина и пр… У меня там произвольная иерархическая структура с боооольшим кол-вом элементов (на 2-3 порядка большим, чем можно было бы показать), которая при том может лихо динамически меняться, и нет решительно никакой возможности узнать высоты DOM-элементов до их непосредственного рендера. Да и после их реальные высоты "протухают" после первого же изменения.


У меня, конечно, задача была сугубо специфичная, и, полагаю, что в большинстве случаев такого треша нет и под 2000 строк писать не требуется. Но всё равно, я полагаю, что вирт. скроллы будут варьироваться в зависимости от возможных исходных данных. И какой-то волшебной кнопки нет.


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

Насколько я понимаю, никто не мешает написать на React компоненту, в которой вся чёрная магия по lazy-loading-у уже будет сделана
Подробно не смотрел, но, вроде как, вот это изделеие может в динамические размеры элементов, судя по демке.

Нет, там не динамическая, а разная высота строк. Достаточно уменьшить ширину страницы и контент начнёт налезать друг на друга.

Насколько я понимаю, никто не мешает написать на React компоненту, в которой вся чёрная магия по lazy-loading-у уже будет сделана, и вам останется только написать что-то вроде <LazyList list={list} component={component}/> и оно полетит.

Ваш пример с иерархией и разными высотами тут не взлетит и придётся пилить 2000 строк. И не такой уж это и редкий кейс, на самом деле, если реализуется адаптивный дизайн, где высота плавает в зависимости от размеров экрана и содержимого. $mol_list же будет рендерить лениво даже иерархию. Другое дело, что если список огромный, то скроллинг в самый конец приведёт к его полному рендерингу, но в этом случае лучше пользователю предоставить удобные фильтры/сортировки, чем заставлять его искать иголку в стоге сена, мотая скролл. Впрочем, есть полноценная реализация virtual-scroll в виде $mol_grid, но она, по понятным причинам, требует предопределённой (но возможно разной) высоты строк.


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

Неделю назад я как раз написал статью про полезный инструмент написанный на $mol, однако, народ увидел в ней лишь "опять вы химичите с ленивыми бенчмарками", хотя я даже и не заикался про "необычайную скорость $mol". Так что толку точно больше не будет, к сожалению. Печально всё это.


Вы даже не представляете как утомительно заниматься этим "самопиаром". Я стараюсь показать людям, как их проблемы элегантно решаются в хорошо продуманном современном фреймворке. Но всё бестолку, только и слышно, что react, flux, redux.


Забавно слушать как разработчики с соседнего проекта плачут из-за многословности флюкса и необходимости вытаскивать очередное состояние в редукс, но боятся $mol как огня с его верблюжьим именованием. Вот уж главная проблема веб-разработки.

Ну, собственно, о чём я и веду речь. У вас есть полу-решение, для полу-задач. Ок. Детально комментировать лень. Если интересно отпишу в личку.


"опять вы химичите с ленивыми бенчмарками"

Ну дык вы же продолжаете заниматься этой пургой. Кто к вам виноват? ССЗБ. Бенчмарки нужно писать беспристрастно и честно, а не сравнивая совершенно разные задачи. Вот какое может быть отношение к человеку, который так "читит"? Вы правда хотите, чтобы после такого epic-fail-а вас воспринимали всерьёз?


Печально всё это.
Но всё бестолку

А вам не кажется, что вы что-то делаете не правильно? Стоя на асфальте, обутым в лыжи, странно удивляться косым взглядам. Начните писать статьи нормальным беспристрастным языком ― толку будет много больше. А до тех пор пока вы пытаетесь других разработчиков лишь передразнивать и вообще продолжать так ёрничать ― наивно ожидать какого-то делового подхода к вашим трудам.


А пока, что в комментариях, что в ваших статьях, я вижу лишь, гхм, Дартаньяна, окружённого… несмышлёнными людьми.


Вот уж главная проблема веб-разработки.

Я не думаю, что вы главная проблема веб-разработки ;) Есть проблемы и поважнее.

Это решение для 80% задач. Для остальных 20% есть довольно простое API, позволяющее реализовать любую другую форму ленивости.


Очень жаль, что вы не слушаете, что я говорю. Почему вы не считаете virtual-dom читерским по отношению к реализациям с текстовыми шаблонами? Почему вы не требуете от этого читерского ленивого реакта "честной" реализации с полным удалением всех узлов, созданием их снова и восстановлением позиции скролла и фокуса, как у "честного" SAPUI5?


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

Реакт позволяет по минимуму трогать DOM, но если скажешь ему отрендерить 10000 элементов, он послушно это и сделает. повесив вкладку на пару секунд.

Скоро React переедет на новый движок Fiber, и такого больше не будет происходить. https://github.com/acdlite/react-fiber-architecture



На GC плохо влияет большое число долгоживущих объектов (создать и тут же уничтожить объект — сейчас почти ничего не стоит)

Откуда ты узнаёшь всё это?)
Что значит долгоживущих? Достаточно чтобы он просто жил и это уже будет влиять на GC или влияет его удаление после того как он долго пожил?

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


https://hacks.mozilla.org/2014/09/generational-garbage-collection-in-firefox/

Соглашусь что тут разные подходы в целом, оверхед сравнивать можно долго и ответа что лучше в этом плане скорей всего получить не получится, да и спорить наверное бессмысленно. Просто с начала прозвучала мысль, что изменяемые структуры в контексте одного глобального хранилища состояния однозначно плохо. С чем не могу согласится. Ну и подход во vuex мне понравился больше, чем в redux, но это уже очень субъективная оценка. Спасибо за подробный ответ.

Если честно, я плаваю в redux-терминологии. Для меня редьюсер не выглядит так, будто он принимает очередь, а выглядит так, будто принимает стейт и возвращает объект, замещающий какую-то часть в стейте.

Имелась в виду аналогия со списком экшенов. Если представить их в виде массива (есть популярный подход представления такие вещей в виде потоков — rxjs), то на нем можно выполнить операцию reduce, которая пробежится по всем экшенам, аккумулируя стейт, и в итоге вернет конечный. От этого и название — reducer, так как так называются передаваемые в reduce коллбэки.

UPDATE: более того, у redux-редьюсера даже сигнатура та же: (state, action) => state.

Не буду делать вид, что понял, зачем экшены живут в списке, и в чем тут сходство сигнатуры, когда редьюсеры в JS имеют вид (accumulator, currentValue, index, array) => value, но за объяснение спасибо.

Скорей не списке, а в потоке/стриме (привет, rx). А по поводу сигнатуры, если откинуть индекс и ссылку на изначальный массив, то получится то же самое. Ведь стейт — и есть аккумулятор, а элементы в стриме — экшены, а редьюсер аккумулирует стейт, пробегаясь по экшенам.

UPDATE: точнее redux аккмулирует стейт, применяя редьюсер к стриму экшенов.
UPDATE2: само собой, это все терминология, и никаких стримов в redux нет

Окей, я понял, спасибо. По прежнему не согласен (стримов нет; а отбрасывать-то можно что угодно, но ведь (state) => state они редьюсером не называют), хотя бы корни терминологии ясны.


А стримы, наверное, возникают, когда делаешь time-travel: берешь initial state и проходишь по массиву случившихся экшенов, чтобы получить состояние в желаемой точке.

А стримы, наверное, возникают, когда делаешь time-travel: берешь initial state и проходишь по массиву случившихся экшенов, чтобы получить состояние в желаемой точке.
Именно! А можно пойти дальше и представить в виде стрима вообще весь жизненный цикл приложения, тогда reduce на любом срезе этого стрима вернет конкретный саккумулированный стейт.
Совершенно верно. Только для этого вовсе не обязательно представлять экшены именно в виде стрима — просто последовательность экшенов, неважно каким образом запущенных. Стрим удобен для контекста (если хотите реактивный стиль), редьюсеру ж по барабану, как он там вызывается.

Я плохо выразился. Имел ввиду "некая последовательность action'ов". Не важно, массивом, стримом или по-одному (из UI, например).


Ну и сигнатура совпадает:
(accumulator (aka state), currentValue (aka action)) => updatedAccumulator (aka updated state)

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


Middleware заменено на plugins во второй версии, но это не мутации, а скорее плагины — это сущности для перехвата мутаций.

Мутации — синхронные и чистые, редьюсеры, так сказать, или транзакции.

А что значит «чистые»? В примере в статье меняется переданный в качестве аргумента объект — или под чистотой вы не имеете в виду «pure function»?

И плюс, между мутациями и редьюсерами фундаментальная разница. Редьюсеры как раз-таки «pure», это чисто функции, они ничего не меняют, а возвращают новый стейт.

Я ошибся, это не чистые функции, а скорее наоборот функции меняющие состояние. Отсюда и использование глагола commit.

Да, только вот commit заканчивает декларативную транзакцию, а тут стор меняется на ходу и сразу.
Перечитал повнимательней, и я не прав. Коммит вызывается из экшена, а изменение происходит в самой мутации.
И то и другое слово изначально — глаголы.
Требовалось два глагола для асинхронных и синхронных методов.

dispatch action (асинхронные)
commit mutation (синхронные)

Evan очень ратует за декларативность и детерминизм в именовании, за что ему огромное спасибо.
Я бы относился к Vue и всей линейке технологий рядом — как к ревью и рефакторингу более старых технологий.

Evan переосмысливает и пере-создаёт технологии, которые без такого ревью и переосмысления — захломятся и обветшают (да-да, без переосмысления интерес к React-у уйдёт, как когда-нибудь и Vue). Но хорошо, что есть такие ребята.

В общем, я рад, что Evan даёт новые имена уже готовым концепциям.
Мне эти имена даже больше нравятся.
Промахнулся.
Это ответ на вопрос: «А какой смысл называть mutation reducer'ом?»

Научите меня писать статьи, которым не ставят минусов. Пожалуйста, очень надо.

Вы ведь понимаете, что после этого коммента минусы появятся?)

Вы мне льстите, я не на столько умный и коварный.

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

Тут в основном декомпозиция идет на уровне собственно бизнес-логики, которая разбивается на небольшие, хорошо изолированные функции.


Плюс есть т.н. «модули», когда вся бизнес-логика, относящаяся к некоторой части состояния, выносится и группируется отдельно.

Навскидку похоже на Redux + reselect. Интересно, тут тоже используется мемоизация в getter-ах и неизменяемость store-а?
и неизменяемость store-а

mutations: {
        ADD_NOTE(state, note) {
            state.notes.push(note)
        }
    }

похоже, что нет

Не знаю начет Redux + reselect, но похоже на MobX. Иммутабельности нет, vue наоборот не любит, когда вы заменяете объект целиком.


С мемоизацией сложный вопрос. С одной стороны в явном виде ее нету. С другой стороны, при использовании геттеров ожидаемым образом — то есть в качестве computed-свойств в компонентах — vue сама следит, какие computed-свойства пересчитать, то есть какие геттеры вызвать.

А подход MobX можно использовать без кучи тупого кода? В смысле стор делать полноценным объектом и мутации его вызывая его методами?

Для этого vuex не нужен, скармливаете любой объект vue.js, она сама будет следить за свойствами. Вместо геттеров — computed (и watch), вместо экшнов — methods.


vuex, в некоторой степени, дань моде на ФП подход в UI; в отличие от React тут действительно есть реактивность и UI перерисовывается без создания нового объекта.

Я имею в виду не «вместо», а обертки, создаваемые автоматически, прозрачно для компонента, он дергает геттер, не подозревая, что это computed.

Угу, уже понял, но лично мне не по душе ФП с разделением состояния, доступа к нему и его мутации в глобальных вещах, ООП подход ближе, а ФП только там где нет сложной логики состояния, те де рндереры реакта.

Так нет никакого «вместо», под капотом это так и работает.


Как я уже и сказал, можно спокойно управлять стейтом без vuex и ФП.

Проясните момент, вы предлагаете использовать Vue.js и Vuex? Хорошо, я того же мнения.
А зачем вы приводите пример для устаревшей версии Vue.js? 2.0 вполне зарелизилась.
Ибо: "$dispatch and $broadcast have been removed in favor of more explicitly cross-component communication"

Это же для новичков самая подстава, когда они начинают знакомиться с фреймворком, а потом выясняется, что пример, который они изучали, не имеет ничего общего с текущей реальностью.
А зачем вы приводите пример для устаревшей версии Vue.js? Ибо: "$dispatch and $broadcast have been removed in favor of more explicitly cross-component communication"

Вы немного ошиблись — это другой dispatch (убрали вот этот).
А, это их собственный метод, спасибо, понятно.
Sign up to leave a comment.

Articles