?

Log in

No account? Create an account

Стой под стрелой

Поступки и мысли, о которых могу вспомнить не краснея

Previous Entry Поделиться Next Entry
Om, React и все-все-все
усы2
tonsky

React.js — это библиотека, недавно перевернувшая JavaScript-мир с ног на голову одной простой идеей: давайте оперировать не настоящими DOM-елементами, а JavaScript-классами, их изображающими. Синхронизацию этого симулякра с настоящим браузером отдадим библиотеке.

Оказалось, что если не трогать DOM, работать с объектами настолько дешево, что можно их даже не хранить, а всегда дергать функцию state → pseudo-dom, генерирующую каждый раз новый псевдо-дом. Это первое достижение React-а, сделать двухстороннюю синхронизацию данные-интерфейс односторонней. Мы всегда пишем, как из данных получить UI, и никогда не пишем, как один UI перевести в другой UI. На всякий случай еще раз: подход «а вот по этой кнопке скроем панельку» был хорош до определенного предела, пока сайт был в основном статический, с немного меняющимися частями. Сейчас же то, что рисуется в браузере при загрузке, и то, что там будет через 2 минуты, может не иметь ничего общего вообще. Поэтому подход «дайвайте опишем, как эволюционирует наш DOM» работает не очень из-за комбинаторного взрыва — слишком много способов, как он может эволюционировать.

Второе достижение React-а в том, что они реализовали концепцию «всё свое ношу с собой», придав компонентам адекватную стандартизованную структуру. Грубо говоря, в момент создания компонент сам себе нужные ресурсы выделяет и сохраняет в собственный стейт (таймеры, сокеты, листенеры), а в момент удаления — прибирает. Звучит банально, но здесь важен факт, что это есть. Ситуация аналогична проблеме неймспейсов — яйца выеденного не стоит, но в JS их забыли и каждый разработчик вынужден пускаться в собственную езду на лыжах по кирпичам. DOM-листенеры тоже, кстати, крепятся к компоненту, но уже самим react-ом. Это опять же важно для highly dynamic интерфейсов — руками за всеми ресурсами следить и замаешься, и не уследишь.

Получаем, что react-овские компоненты это такие маленькие самостоятельные кирпичики, которые действительно можно складывать в стену и они сами будут работать, не захламляя приложение в неожиданных местах типа регистрации в top-level listener или создаваемых, но никем не удаляемых setInterval.

В общем, я примерно всю революцию уже описал. Можно делать самодостаточные, переиспользуемые компоненты (у них честное воткнул-и-готово), очень простой и мощный data binding (произвольная js-функция state→dom, и не надо выкручиваться в ограничениях template bindinds, и вообще html с шаблоном не нужен), быстрая синхронизация с DOM. React.js маленький-легковесный и это, в целом, opinionless библиотека, которую можно совмещать с чем угодно. Используется Фейсбуком и Инстаграмом, куча баек про то, как интерфейс, переписанный на React, избавляется от проблем со скоростью отрисовки. Но главное, что с ростом количества динамических частей на странице код не начинает катиться в говно экспоненциально. В целом, прорыв уровня jQuery.

Теперь Om. Om — это ClojureScript-биндинг к React-у от David Nolen, главного коммитера ClojureScript, и первая вещь, которую надо про него понять — это не биндинг. Да, он использует React, оборачивает его в cljs интерфейсы, но на самом деле под этой (достаточно выгодной) этикеткой продает свои, совсем другие идеи.

Первая, и очень хорошая — зачем нам рендериться на любое изменение стейта, давайте рендериться один раз на requestAnimationFrame. Мы получим те же 60 fps и еще меньшую площадь соприкосновения с DOM, чем в React. За счет этого, например, он обогнал голый React в ToDoMVC перфтесте.

Вторая — это управление состоянием всего приложения. Если React в этом смысле был достаточно нейтрален (ну, ты, это, давай там сам как-нибудь сам), то у Om насчет состояния вполне четкие планы. Дэвид постаралася сохранить переиспользуемость компонентов и добавить несколько бонусов сверху. Если коротко, то состояние всех компонентов в Om — один большой atom, персистентная структура, а все компоненты получают view в эту структуру на то место, которое относится непосредственно к ним. Т.е. они получают такой handle (в терминах om — cursor), через который они могут читать кусочек этого большого дерева и писать в него. Если компоненту нужны подкомпоненты, можно создать под-курсор из своего курсора, например.

Эта простая идея дает два бенефита:

Возможность показывать в разных местах страницы одно и то же значение буквально. То есть, например, слева у нас таблица сотрудников (компонент table), и в колонке «пол» у них или Man, или Woman. А справа у нас text input для редактирования списка полов, и мы в нем меням Man на Male и в таблице все автоматом синхронизируется. Потому что оба компонента работают с курсором на одно и то же место в атоме. Из-за особенностей реализации text inputs в React можно даже сделать так, чтобы значение в таблице менялось по мере набора букв в text input.

Второй бенефит — это возможность работать с состоянием всего приложения как с чем-то цельным. Это персистентная структура, значит можно хранить историю состояний всего на 100 (например) версий назад, каждое состояние иммутабельное и все они разделяют кусочки, которые не изменялись. Элементарно прикручивается undo, причем на любую версию: ты просто говоришь om-у (а он react-у): давай сейчас вот это состояние рисуй, и react сам вычисляет оптимальный способ его нарисовать из текущего dom: где что удалить/добавить/подвинуть, чтобы получить целевое. Палитру History в фотошопе видели? Короче, бонус в том, что управление состоянием вынесено за скобки. Можно прикрутить логику синхронизации с сервером, undo, сохранение в localStorage — и всё это будет снаружи и отдельно, а не размазано по GUI-компонентам.

Там не всё идеально — документация мутная, курсоры излишне хитрожопо сделаны и не описаны (точнее, сейчас описаны, но это я доку написал), и вообще оно alpha. Но это уже, наверное, самый востребованный GUI-фреймворк на cljs — я думаю, из-за hype вокруг React + имени создателя. Я не то чтобы рекомендую, я скорее популяризирую хорошие идеи, плюс это еще такой спасательный круг тем, кто полезет в Om разбираться.

Вот здесь можно посмотреть на мои эксперименты: игра «41 носок» вживую, исходники.



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

Все-таки нет, если для отрисовки так значения передать можно, то как передать, скажем, глубоко вложенному text input строку "Ivanov Ivan" чтобы он смог изменное значение обратно наверх донести?

А не знаешь, на джаваскрипте такое дерево как в Ом уже написали джаваскриптеры ?

+- шось подобное есть: https://github.com/mquan/cortex/

Я подумываю поверх мори сделать.

Отличное интро, спасибо.

Пара опечаток:
>Дэвид постаралася
>в колонке «пол» у них или Man, или Woman

Ой, давайте я тоже пару слов. скажу. Я для небольшой админки решил изучить вопрос с тем как нынче можно писать фронт и вот к каким выводам пришел.

Om:
- Вся эта тема с курсорами умирает от одного простого факта — в реальных приложениях мы не работаем с курсорами, каждый компонент может зависеть от разных частей стейта (текущего юзера, списка всех элементов коллекции, конкретного элемента коллекции). В OM для этого придумали graft-ы, которые раньше были эксплицитными, теперь стали имплицитными. Но того, что это хаки модели этого не меняет. om/transact! с ними перестает работать как ожидается, да и вообще, мне всегда нужно помнить о том, как оно там внутри устроено, я не могу просто так сделать map, filter или еще что угодно с моими данными, om-у плохо станет;
- Идея о том, что модификация стейта должна исходить от компонентов, т.е. view мне не до фига нравится и превращается в коллбэчный ад. Стейт — это атом, с обычной кложавской структурой в нем, так? А вот нифига это в OM не так. Модифицировать все это нужно только через специальные om/transact! которому нужно передать курсор, который вроде как совершенно view-штука, почему от него зависит модификация данных?;
- У него громоздкий и неудобный интерфейс с кучей интерфейсов. Кроме того курсор, переданный в компонент, в зависимости от мест где используется является разными сущностями! В рендер-стадии это данные, в коллбэках части это собственно курсор. Ужас. Не позволяет, например, писать общие для WillUpdate и рендер-стадии замыкания-хелперы компонента;
- Помипо курсоров, о которых ты написал, в Om есть еще и локальный стейт компонентов. Который тоже вклинивается в процесс рендеринга и определения что и когда следует отрендерить. И у которого тоже есть свой особый набор функций, исключительно через которые с ним можно работать;
- Обычно, если я чего то глобально не понимаю, то сначала считаю, что я тупой и чего-то не понял. Однако, в ридми Om-а есть ссылка на https://github.com/sgrove/omchaya/, самое крупное, что написано на Om и в опенсерсе, как я понимаю. И что же мы там видим? Что пацаны положили на все эти темы с курсорами и локальными стейтами! И тупо используют обычные кложавские структуры. Все модификации стейта происходят без всяких om/transact! а посылкой сообщения в контроллер, который уже напрямую меняет атом. Т.е. в самом крупном Om-проекте ссылка на который стоит в ридми Om НЕ используется ни одна из фичей Om.

Как итог, мне кажется Om типичным оверинжинирингом, для решения локальной и редко возникающей задачи (создания reusable компонентов, у которых есть локальный стейт, который они могут менять, но он при этом прозрачно для них является частью глобального) нагородили кучу ада, мешающего во всем остальном. Как ооп с классами. А вот это "+ имени создателя" ключевым фактором именно его успеха, а не других подходов.

Reagent (https://github.com/holmsand/reagent):
Самый мимимишный на первых взгляд из всего, однако под капотом тоже всякий ад, который нужно держать в голове, что бы оно работало. Переопределяется атом для определения когда и для чего нужно пускать рендер. Хитрое кеширование созданных компонентов. Лично я так и не понял как ко всему этому прикрутить правильно полный lifecycle реактовских компонентов.

Quiescent (https://github.com/levand/quiescent)
Вот он, мой фаворит! Это именно враппер вокруг реакта, не делающий ничего лишнего, кроме нужного: определения shouldComponentUpdate на сравнение кложавских структур и небольшого апи для создания компонентов с lifecycle-коллбэками реакта. Все, сотня строк кода.

... не влез в комментарий, гг

Я написал свою админку на Om, по ходу пробуя reagent. Получилось вполне, но мне не нравилось. Какая то лапша из коллбэков, какие то графты, какие-то дереференсы курсоров. Что это и зачем это. Во время написания на Om не покидает ощущение, что даже на js и голом React все было бы проще и лучше.

А потом переписал все на Quiescent и остался до фига доволен ) Пришлось изобрести небольшой фреймворк, с понятиями View и Controller, но это натурально 50 строк кода. Теперь компоненты это тупые функции, передавать в них можно все что угодно, стейт это просто данные (слепок на определенный момент времени, как и должно быть), можно модифицировать, композировать, разделять как угодно. Все коллбэки в компонентах делают одно — посылают сообщение в core.async-канал, который читает контроллер и меняет стейт. Контроллер же запускает перерендер вью. Кода вышло меньше, он стал понятнее и структурированнее. И, внезапно, все это заработало значительно шустрее, чем с Om :) Что касается реюзабельных компонентов, то если говорить о них в масштабе приложения, то никаких проблем нет. А если про "а давайте запилим draggable-компонент один раз для всех в мире", то тут посложнее, но вроде тоже вполне решается без курсоров.

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

По статье:

> Первая, и очень хорошая — зачем нам рендериться на любое изменение стейта, давайте рендериться один раз на requestAnimationFrame. Мы получим те же 60 fps и еще меньшую площадь соприкосновения с DOM, чем в React. За счет этого, например, он обогнал голый React в ToDoMVC перфтесте.

Вообще, на сколько я понимаю, бьет реакт он за счет прежде всего эффективности сравнения структур данных в кложке против JS, разве нет? Иммутабельные перзистентные структуры сравниваются тупо по "указателю", им не нужно проходиться по всех структуре, как в js-е.

> Возможность показывать в разных местах страницы одно и то же значение буквально. То есть, например, слева у нас таблица сотрудников (компонент table), и в колонке «пол» у них или Man, или Woman. А справа у нас text input для редактирования списка полов, и мы в нем меням Man на Male и в таблице все автоматом синхронизируется. Потому что оба компонента работают с курсором на одно и то же место в атоме.

Все это будет работать ровно так же и без всяких курсоров.

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

Опять же, без курсоров это точно так же будет работать. Только проще, без дополнительных запилов коллбэков типа tx-listen

Т.е. реакт это очень круто, лучшее что случалось в веб-фронтом в последнее время. Но вот кроме Om существуют еще и другие подходы, на мой взгляд куда более правильные.

Короче однажды я попробовал реакт и за часа 2 не выкупил как решить свою задачу(я не отрицаю, что туповат и туговат), но на ангуляр я начал колбасить за считанные минуты. Это нормальный порог для реакта?
Если есть опыт на clj проще ли в освоении ом по сравнению с реактом, или это примерно те же яйца?

Я как-то наоборот на React начал колбасить за минуты, а в Angular так и не въехал, слишком запутанно.

После ангуляра Om с реактом как глоток чистого воздуха. И отладка гораздо более вменяемая, никаких бесконечных циклов скоуп вотчеров, никаких невменяемых ошибок где-то в кишках. Особенно хорошо это дело сочетать с Sente (https://github.com/ptaoussanis/sente/). Вообще, относительно большое (~80 KLOC) ангулярное приложение на ом перенеслось за неделю, похудев изрядно и избавившись от кучи идиотских багов.

Но курсоры, конечно, доставляют странного геморроя. Особенно по части "типов"-- из вектора оно само делает IndexedCursor, из списка не хочет, причем часто это выглядит совсем не так как ожидаешь. Но это, пожалуй, единственное что мешает жить (да и то, насильное приведение к вектору списков как workaround не напряжен).

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

Например, чтобы получить курсор на int, я кладу его в вектор длиной 1:

(def state (atom {:timer [0]}))

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

Другое дело, что для сборки компонентов нужно использовать микс из JS & HTML что неудобно. Но это обычная неудобность, она не растет с ростом слоности проекта.

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

(Удалённый комментарий)
Есть еще момент, я собневаюсь что из выбора react / backbone / angular - кто-то имеет значительное преимущество. Что-то чуть лучше в одном, что-то в другом. Восновном-же те-же яйца в профиль.

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

Edited at 2014-04-15 14:19 (UTC)

Да, есть такой способ — но он меееееедленный :) Прошлое десятилетие ему уже.

без темы (Анонимно) Развернуть
Прочитала про React, выглядит аппетитненько. Минуя 1000++ непонятный комментарий, хочу задать очень чайницкий вопрос: если "и вообще html с шаблоном не нужен", то... эм... кто нам будет генерить html? Что рекомендуете использовать в связке с React для сборки html-блоков?

Так реакт и генерит хтмл сам. Просто описывается хтмл прямо в методе render у каждого компонента, а не где-то снаружи.

Никита, подскажи, пожалуйста, такое: если я обычным свопом где-то вне компонентов меняю атом с состоянием приложения, но меняю при это одну какую-то ветку, Ом пересчитает и отдаст реактовскому диффу только те компоненты, в курсоры которых входит эта ветка, или всё приложение?

Om пошлет shouldComponentUpdate=true для root. Дальше обычный React бы перерисовал всё поддерево (реакт всегда перерисовывает всё поддерево, если под-ветки не вернут shouldComponentUpdate=false, а по дефолту они возвращают true). Вот, достижение Om основное в том что он реализует shouldComponentUpdate не пессиместично (как react, always false) а сравнивает предыдущее и текущее значение курсора (подветки в атоме то есть). Соответственно по факту перерисуются только компоненты, у которых курсоры смотрят на те подветки которые поменялись. Правда, как выяснилось, Om сравнивает значение по ссылке, а не по содержанию. Т.е. (swap! (atom {:a [0]}) assoc :a [0]) перерисует :a потому что это будет новый вектор, хоть и такой же :)

Мне вот всегда не понятно c Virtual DOM почему он оказывается быстрее? Почему браузер сам не может нормально буфферизовать изменения DOM-а ?

Там типа ffi, поход из тихого уютного js-мирка во внешний злой неродной C++ мир.

Почему браузеры не делают быстрой js-прослойки непонятно, да.

(Анонимно)
Подождите ребятушки. Ребятушки, одну секунду.

Вопрос по движку реакта. Сам реакт инициализируется с помощью нескольких анонимных функция, в одну из которых передаются массив (или объект, не помню) всех возможных модулей реакта. И этот массив почему-то использует какой-то странный способ наименования (или учета, не знаю как сказать) модулей, т.е. используется как имя модуля, нечто вроде "./ReactContext", а так же числовой "адрес" (назовем его так) каждого модуля.

Почему не достаточно только одного? Либо id модуля, либо его имя?

Подождите ребятушки. Ребятушки, одну секунду.

Вопрос по движку реакта. Сам реакт инициализируется с помощью нескольких анонимных функция, в одну из которых передаются массив (или объект, не помню) всех возможных модулей реакта. И этот массив почему-то использует какой-то странный способ наименования (или учета, не знаю как сказать) модулей, т.е. используется как имя модуля, нечто вроде "./ReactContext", а так же числовой "адрес" (назовем его так) каждого модуля.

Почему не достаточно только одного? Либо id модуля, либо его имя?

Посомтрите в сторону Morearty.js

(Анонимно)
https://github.com/Tvaroh/moreartyjs

Возможно, вам будет интересно: надстройка над React в духе Om, но на голом JavaScript без лишних зависимостей. Проявила себя очень хорошо на проекте. Пришлось её написать после того, как с голым реактом закопался.

Re: Посомтрите в сторону Morearty.js

Ого, персистентные мапы на JS :) Ну круто, это по сути Ом и есть

  • 1