?

Log in

November 2016   01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
cartoon

NestedReact 1.0 beta. Hierarchical Checklist Demo.

Posted on 2016.08.08 at 21:50
Tags:
Пример из NestedReact 1.0 beta, которая сейчас в бранче develop.

Это вот такой иерархический чекист с правилом - группа выбрана тогда и только тогда, когда все дерево под ней выбрано. 79 строк кода за вычетом комментариев и пустых. В основе - новый движок транзакционных моделей, написанный на TypeScript (NestedTypes 2.0 beta). Кругом ES6 классы. Ни одного стора нет. Просто составное состояние в корневом элементе.



Так вот, здесь, не смотря на data binding и классическое ОО в слое данных - unidirectional data flow, и вовсю работает pure render оптимизация. Очень рекомендую на код посмотреть - я его закомментировал в литературном стиле. Там смотреть то почти не на что - кода тьфу.

https://github.com/Volicon/NestedReact/tree/develop/examples/checklistTree

И то и другое возможно на классических моделях без каких-либо проблем. Не верите - запустите и посмотрите в React Tools как там рендер происходит. :) Оно уже собрано, надо просто сделать клон репозитория, и открыть index.html. Да, эти линии - в них можно кликать мышкой и писать там. Это input на самом деле.

UPD: Теперь оно сохраняет и восстанавливает состояние работая с localstorage. В качестве демонстрации, что любое состояние сериализуемо.

ЗЫ: Все время слышу последнее время - mobx, mobx. Было бы клево, если бы кто-нибудь сделал аналогичный пример на mobx.

TypeScript: https://github.com/mikhail-aksenov/mobxChecklist
ES6: https://github.com/kvasdopil/checklistTree

Спасибо, ребята. Выглядит действительно очень похоже. Оба примера по 90 строк. Пример с NestedReact занимает столько же, только в отличии от примеров mobx он сохраняется в LocalStorage. Бесплатная сериализация - это самое основное отличие. Ну, могу еще для демонстрации отличий валидацию на текстовое поле навесить. :) У меня это делается в модели - "name : String" -> "name : String.has.check( x => x )", и поле с именем получит стиль error, если значение пустое. Красным станет, например. Само.

Полный список возможеностей сверх mobx - two-way data binding, декларативная валидация, миксины, коллекции с индексами по id вместо простых массивов, сериализация (с id-ссылками и без), REST.

ЗЗЫ: На всякий случай напоминаю, что этим волшебным моделям NestedTypes уже два с половиной года. Они как бэ пораньше mobx появились, у нас в Verizon поверх них уже 100К строк наколбашено. Я считаю, это освобождает меня от дурацких объяснений "а зачем оно написано если есть mobx". И если честно - я сильно сомневаюсь, что mobx сравним с NestedTypes по производительности на сериализации и синхронизации с сервером коллекций сложных объектов по десять тыщ элементов. У нас на таком сейчас в 2.0 beta субсекундный отклик, например (часть требований). А вот, например, Backbone на таком - сдохнет, он заблокирует тред браузера секунд на 10. И не только бэкбон :).

ЗЗЗЫ: Это вообще довольно глупый вопрос - "а зачем". Затем, что можем. Не хотим страдать с редуксами, как все, и не собираемся дожидаться, когда кто-то что-нибудь сделает с этим.

Comments:


ЛёШа Фу!
kvasdopil at 2016-08-09 10:20 (UTC) (Link)
Персистенса из коробки в mobx нет, это да. Сериализацию сделать несложно через JSON.stringify(model.toJs()), но десериализации нет, ни быстрой ни медленной)

На mobx вам указывают совершенно справедливо - с точки зрения апи библиотеки практически ничем не отличаются:
Точно так же есть стор (ака @predefine у вас или класс с @observable полями в mobx)
Точно так же компоненты биндятся к стору (через @define у вас или @observer в mobx)
Точно так же есть транзакции для изменения стора (ака @action в mobx)

Я не спорю что на практике у вас там множество нюансов учтено и естественно для ваших требований ваша либа гораздо лучше походит (см ту же сериализацию), но
1) утверждение про "самый простой способ управления состоянием" по крайней мере спорно
2) не очень понятны перспективы использования вашей либы вне компании, при живом-то mobx (и при том что у них версия 2.4 а у вас 0.6)

Что безусловно не отменяет что вы большие молодцы. Если эта технология два года назад у вас уже была то вы очень круты.
Gaperton
gaperton at 2016-08-09 17:50 (UTC) (Link)
> Точно так же есть стор (ака @predefine у вас или класс с @observable полями в mobx)

@predefine это не стор, это определение модели. Которая, на минуточку, рекурсивна, и формирует дерево. В этом примере никакого "стора" нет. Вообще ни одного синглтона.

> Точно так же компоненты биндятся к стору (через @define у вас или @observer в mobx)

Никак компоненты не байндятся к стору, вообще :). Компонента работает с составным локальным state. Я могу три таких компоненты вставить в одну страницу - и они будут прекрасно работать каждая со своим состоянием, не мешая друг другу.

И @define называется словом 'define' а не 'observe' не просто так. Он много что делает помимо создания модели для локального state и 'observe'. Например - собирает миксин для pure render, когда это надо. Или (чего в данном примере нет), возвращает в react классы миксины, которые были в React.createClass.

> Точно так же есть транзакции для изменения стора (ака @action в mobx)

Пока не увижу этот пример на mobx работающим - слова "точно также" мы опустим. Про стор мы уже выяснили, что его в примере нет? Значит, уже не так же. А как-то очень сильно по другому.

> На mobx вам указывают совершенно справедливо - с точки зрения апи библиотеки практически ничем не отличаются:

Не вижу в API в принципе ничего общего. Кроме факта, что в mobx тоже наблюдаемое состояние (оно дофига где есть, вообще-то - еще knockout его делал), и тоже есть класс-декораторы с @ (если они есть - это совсем не означает, что они одинаково работают). И не понимаю, в чем "справделивость" этих указаний, которые делаются вместо приложения элементарных усилий чтобы понять, о чем речь. Вы ж не веганы, чтобы по любому поводу лезть к людям и заявлять - "я веган!"

> утверждение про "самый простой способ управления состоянием" по крайней мере спорно

Поспорьте. Желательно, в виде примера кода - самый лучший способ спорить. Мой пример - вот. С удовольствием посмотрю ваш.

> не очень понятны перспективы использования вашей либы вне компании, при живом-то mobx (и при том что у них версия 2.4 а у вас 0.6)

Если кому-то "живой mobx" или циферки в номерах версий мешает смотреть на другие технологии - я с этим ничего поделать не могу. :) Посочувствовать разве. Мне ничего никогда не мешало (я, к примеру, в курсе, как работают транзакции в mobx. По другому они там работают, ни в одном месте не "так же"). Профессиональное любопытство - оно или есть, или его нет. И оно со стадным чувством плохо дружит.

Edited at 2016-08-09 06:33 pm (UTC)
Курилыч
kurilka at 2016-08-09 19:42 (UTC) (Link)
Жжесть, 20 раз редактировать комммент...
Gaperton
gaperton at 2016-08-09 20:31 (UTC) (Link)
Я к комментам отношусь серьезно, как к части поста, так что можно и 20 раз. С учетом опечаток. Он за эти 20 раз принципиально не поменялся, не так ли?

ЗЫ: Этот коммент уже редактирован 1 раз.
ЗЗЫ: Вообще - мой коммент, что хочу, то с ним и делаю. Редактировано 2 раза.

Edited at 2016-08-09 08:38 pm (UTC)
ЛёШа Фу!
kvasdopil at 2016-08-10 15:15 (UTC) (Link)
> Поспорьте.
Да пожалуйста. https://github.com/kvasdopil/checklistTree

В принципе, видны преимущества вашей библиотеки:
1) из коробки есть сериализация и контроль типов
2) прикручен 2-way биндинг к контролам форм
3) (наверное) оно быстрее работает на ваших задачах

С другой стороны, от синтаксиса апей хочется разрыдаться.
Gaperton
gaperton at 2016-08-10 21:25 (UTC) (Link)
А что, выглядит совсем неплохо. Кода чуть побольше, но незначительно.

Индивидуальная подписка каждого экземпляра на обновления своих данных с автоматическими локальными апдейтами - это конечно выглядит очень круто. Это то место, где эти примеры работают по разному - NestedReact подписывает на обновления только state, и update идет сверху, отсекая лишнее pureRender-ом.

Локальные обновления тоже можно, но их надо включать отдельной строкой (listenToProps = 'propName1 propName2' ) и выносить state в store. Мы так не любим делать. Хотелось бы посмотреть, как оно будет работать в случае со state - для чистоты сравнения. И что там тогда с рендерами будет.

А так да, по возможностям наблюдения за состоянием получается практически равноценно. Отсутствие data binding, сериализации, валидации (в этом примере не используется) - это, пожалуй, основное.

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

Edited at 2016-08-10 09:43 pm (UTC)
Gaperton
gaperton at 2016-08-10 22:11 (UTC) (Link)
Ну и спасибо, конечно. Отличный пример.
ЛёШа Фу!
kvasdopil at 2016-08-11 06:00 (UTC) (Link)
Вам тоже спасибо, идея линков меня заинтересовала. Задницей чую что их можно получить в mobx из коробки. Напишу если найду.
ЛёШа Фу!
kvasdopil at 2016-08-11 08:44 (UTC) (Link)
В общем от mobx функциональности линков добиться не получилось. Они там внутри используются (ака BoxedValue), но их проблематично достать из рендера.
В принципе, всё легко решается наколхоживанием линков руками (см https://github.com/kvasdopil/checklistTree/blob/master/src/link.js) и валидаторы туда засунуть не проблема. Правда есть сомнения, что оно будет прям настолько уж удобно.

Оно будет несколько удобнее, если формы описывать живыми реактовскими компонентами. Просто мы вместо этого пишем что то типа

popupForm({
 title: "Edit User",
 data: this.object,
 fields: [{
  name: 'name',
  validator: validators.UserName,
 },{
  name: 'icon',
  type: IconSelect
 }]
})

и, раз у нас на каждую форму всё равно магический враппер накручен, то и смысл в ништяках типа линков пропадает.

А как у вас валидация сделана? Мож я не понимаю чего-то?
Gaperton
gaperton at 2016-09-06 02:24 (UTC) (Link)
Все просто. Во-первых, реализация линков вынесена в отдельный пакет.

https://github.com/Volicon/NestedLink

Там кода совсем немного, и он простой. Его можно почитать.

Во-вторых, эти линки легко прикручиваются к любому источнику данных. Сделать привязку к mobx - это немного поправить метод Link.state. Надо сделать линк, который вместо setState при записи тупо пишет в проперти объекта. И все.

Еще надо поправить Link.all(). По хорошему - эта пара поправленных методов выносится в базовый класс, рядом с которым кладется новый подкласс линков.

Вот так это делается для NestedReact: https://github.com/Volicon/NestedReact/blob/develop/src/nested-link.js
Gaperton
gaperton at 2016-09-06 02:28 (UTC) (Link)
Могу добавить, что эти линки из NestedLink мало того, что дохрена могут (что, впрочем, в случае mobx по большей части не нужно, но валидацию на коленке, например, могут), но лишены их традиционных недостатков о которы пишут в сети. Например, они умеют кэшируются в компоненте. Что не ломает pure render.

Последнее, впрочем, тоже в случае mobx не особенно нужно, наверное. Там локальный render ведь всегда, вроде. ХЗ, как это с линками будет дружить при их передаче в другие компоненты.
Gaperton
gaperton at 2016-09-06 02:35 (UTC) (Link)
Валидация сделана так. http://slides.com/vladbalin/deck#/

Каждый класс модели и коллекции умеет валидировать себя, и метод model.getLink( 'attr' ) протаскивает validation error модели как еще один проперти линка.

Принцип, как линки используются для валидации, объяснен здесь: https://medium.com/@gaperton/react-forms-with-value-links-part-2-validation-9d1ba78f8e49#.78tgyjp5m

Этот трюк - это по настоящему круто, кстати. Позволило сократить код форм в нашем продукте примерно в половину. ИМХО - это главнейший аргумент в пользу линков. То, что они позволяют писать минус пропс - это ерунда в сравнении с валидацией.

Edited at 2016-09-06 02:37 am (UTC)
Трещиноватые коллекторы. Инструкция.
maksenov at 2016-08-10 06:07 (UTC) (Link)
Прикладываю вариант на mobx.

Принципиальная разница в том, что я вынес корневой стейт в синглтон стор. Можно было его запихать в состояние компонента, но так мне как-то привычнее.

Reaction - грубый аналог watcher в NestedReact с той разницей, что сравнение неглубокое, поэтому реакция натравлена на изменение свойства checked дочерних элементов.

Буду рад дополнительным вопросам.

https://github.com/mikhail-aksenov/mobxChecklist
Gaperton
gaperton at 2016-08-10 21:37 (UTC) (Link)
Ага, reaction это аналог watcher и событий 'change'. И транзакции есть, точно так же.

На добавление и удаление одного элемента в массив подписаться можно (если у нас транзакция, и там серия array.push), или только на изменение всего?

И я хочу уточнить про "сравнение не глубокое".

x - это { a : [ { b : 5 } }. Если на месте изменить b, reaction на a не сработает? Но если компонент такое получит через props, и нарисует, то он изменение все-таки заметит?
Трещиноватые коллекторы. Инструкция.
maksenov at 2016-08-11 02:24 (UTC) (Link)
Reaction работает примерно так: в первой функции определяем на что реагировать, а значит если подать только массив, то реакция будет на удаление/добавление - изменение контента не перехватится. Можно сделать autorun - тогда будут трекаться все поля, к которым есть доступ внутри этого autorun, но возможны лишние срабатывания.

Компонент-то заметит, но реакция не запустится, а значит нарисовать он может что-то не то.
Gaperton
gaperton at 2016-08-11 02:51 (UTC) (Link)
А если сделать так - то она будет следить и за удалениями-добавлениями, и за изменениями элемента checked. Так?

        // Set reaction for checked property of every child
        reaction(() => this.children.map(f => f.checked), childrenChecked => {
            if (this.children.length)
                this.checked = this.isChecked
        })


Если мы хотим следить за любым изменением атрибутов объектов массива - это сделать вообще нельзя, или можно как-то?
Трещиноватые коллекторы. Инструкция.
maksenov at 2016-08-11 03:57 (UTC) (Link)
Так - да, потому что будет изменяться и количество элементов в массиве, и свойство.

Это можно сделать через autorun, но в reaction более тонкий контроль.
Gaperton
gaperton at 2016-08-10 21:41 (UTC) (Link)
Еще вопрос - а в состояние компонента его как запихать? Очень интересно посмотреть, как оно после этого будет рендериться.

Кстати, сейчас оно, в отличии от другого примера, каждый раз рендерит корневой элемент при любом изменении. Почему так? Что на это влияет?
Трещиноватые коллекторы. Инструкция.
maksenov at 2016-08-11 05:46 (UTC) (Link)
По поводу состояния я еще посмотрю.

Ререндер скорее всего из-за того, что у меня в ElementStore root - @observable, а в другом примере просто переменная. Можно просто объявить его const в index.tsx и посмотреть что там и куда.
Gaperton
gaperton at 2016-08-10 22:34 (UTC) (Link)
Еще вопрос про TypeScript. Какова обычная практика его использования с React - tsx-файлы аннотируют типами, или только слой данных?
Трещиноватые коллекторы. Инструкция.
maksenov at 2016-08-11 02:47 (UTC) (Link)
Здесь я не слишком сильно заморачивался, если честно, но на работе мы аннотируем и tsx достаточно для того, чтобы не путаться (то есть any есть, но только когда совсем припрет). Самые большие проблемы с аннотациями в компонентах у меня возникают при интеропе с жс либами, например с Leaflet (у него были некорретные тайпинги) и HandsonTable (который вообще весьма забавен).

Например, List в рабочем проекте я перепишу так:

const List = ((props: {
    elements: Element[]
}) => {
    return (
        <div className='children'>
            {_.map(props.elements, item => <Item key={uuid.v4()} element={item} />)}
        </div>
    )
})
Gaperton
gaperton at 2016-08-10 20:45 (UTC) (Link)
Вот, совсем другое дело. Сразу два примера. И даже один на TypeScript - давно хотел посмотреть, как люди этого готовят. Спасибо, парни! Щаз посмотрим.
Previous Entry  Next Entry