Как стать автором
Обновить

Комментарии 60

Видел этот курс, благодарю за перевод! Будем учиться!
Большое спасибо, всегда с интересом читаю ваши статьи и переводы
Хотел дополнить комментарий, в общем потратил 10 минут и ужасно рад что все так просто и легко, по ощущениям проще чем Vue(хотя Vue показался тоже простым) и уж тем более проще AngularJS(делал на нем маленький проект замучился), в общем еще раз большое спасибо вам, как раз ищу легкий фреймворк, чтобы обрабатывать данные с бекенда (NodeJS + Express)

P.S. Прошу не злиться адептов Angular и Vue, я не профессиональный разработчик, это скорее хобби, поэтому не воспринимайте мои слова близко к сердцу, что с меня взять.
по ощущениям проще чем Vue(хотя Vue показался тоже простым)

Их нельзя так просто сравнивать. В vue реактивная магия из коробки, а реакте(по крайне мере в предложенном в статье виде) нет и всю магию(если она нужна, а если нет — заменитель) надо прикручивать сбоку.

производительность которых превышает то, что достижимо с использованием лишь JavaScript

Двойной фейспалм. Нет, ну вы это серьезно?


React — это возможность создавать веб-компоненты

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

JSX. Это — синтаксическое расширение JavaScript, которое позволяет создавать компоненты, используя возможности HTML и JavaScript.

Полная и несусветная глупость, у хтмл в сравнении с js нет никаких возможностей. JSX это просто попытка создать «нормальные» хтмл-шаблоны. И всё это во имя совместимости.

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

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

Сейчас нам достаточно знать о том, что Virtual DOM помогает веб-приложениям работать гораздо быстрее, чем если бы при их разработки использовался обычный JS.

Неверно, vdom является костылём(оптимизацией) изначально несостоятельной концепции(почему она используется — расскажу ниже), при этом эта оптимизация ничего не меняет.

Есть совсем просто: «быстрый» обновляет значение любой ноды за O1, когда как vdom за O(n * const), где n это кол-во элементов в дереве компонентов/элементов, а const некая сложность обновление одного компонента/элемента. Очевидно, что это никоим образом не быстро и целиком и полностью ущербно. Сам vdom лишь влияет на контексту, т.е. сложность обновления в dom выше, чем в vdom. Именно поэтому он быстрее, но быстрее в сравнении с чем?

Теперь про концепцию. Что из себя представляет реакт? Это некий шаблон, вызов которого генерирует dom, который в свою очередь генерирует картинку. Это некий типичный php-way. При каком-либо изменении данных шаблон перевызывается, генерируя новый dom. Очевидно, что такой путь крайне глупый и то решение, которое дал реакт — это не генерировать каждый раз дом, а генерировать vdom. А сам dom иметь мутабельным, накатывая на него обновления, которые есть разница предыдущего/текущего состояния vdom. Это очень типично для любых иммутабельных подходом — они зачастую оказывают несостоятельными.

Почему выбран был такой подход? Ответ прост. Это привычный всем php-way, т.е. эмуляция обычных текстовых шаблонов. Для большинства людей это подход наиболее удобен и понятен.

Очевидно, что из этого описания крайне просто выводится тот самый O1 подход. У нас есть данные, которые отображаются в каких-то элементах. Нам нужно просто запомнить какие данные с чем связаны. Какие данные обновились мы знаем и далее мы просто обновляем связанные с ними элементы. И нам абсолютно неважно сколько там у нас в дереве элементов, хоть миллион, хоть миллиард. Мы просто выкидываем ненужный vdom и изначально несостоятельный подход самого реакта.

И так действительно делается, даже на уровне реакта с его контекстом. А так же на уровне любой state-библиотеки. Но всё это работает на уровне компонентов(ведь реакт не может отслеживать эти связи). Особенно хорошо это работает с реактивностью с тем же mobx.

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

Вы очень категоричны. Боюсь если рассуждать таким образом, то состоятельных решений не существует в природе, все в чём-нибудь кривые и косые. Нет реактивных решений с O(1). Если вы считаете, что у вас такое решение есть, то скорее всего вы либо не понимаете bigO, либо подводные камни того или иного решения. А возможно и то и другое.


P.S. O(n * const) = O(n)

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

На чём основаны эти заявления? Каким образом из «это костыль» следует «всё костыли»? А даже если всё, то каким образом это что-то меняет? Это ведь попросту ахинея, ведь даже если всё костыли, то мы можем измерять этот уровень костыля и лучшим решением будет мене костыльное, менее кривое.

Самое смешное, что ровных поверхностей не существует в природе — что из этого следует? Я не могу что-то назвать кривым или косым?

Нет реактивных решений с O(1)

Я же уже показал O1 решение, либо вы не смогли его понять? Ну тогда объясню попроще — прямой биндинг есть O1 решение по определению. Если ещё проще, прямое изменение абстрактного value в dom-node является O1 решением.

Самое интересное, что даже если вы не понимаете назначения vdom — я рассказал об этом выше. Но даже сейчас вы этого не понимаете. Мне ещё раз рассказать, либо вы сами в состоянии изучить теме до попыток спорить?

Если вы считаете, что у вас такое решение есть, то скорее всего вы либо не понимаете bigO, либо подводные камни того или иного решения.

Я услышу основания для этих заявлений, а так же примеры подводных камней? К тому же, сами эти рассуждения несостоятельны и не имеют смысла, т.к. любой vdom является прямым биндингом и все «подводные камни»(фентезийные) присущи и vdom. И раз тут они не мешают — они не будут мешать в рамках чего-то иного.

P.S. O(n * const) = O(n)

Приравнивая одно к другому вы множите на ноль смысл vdom, т.к. он асимптотически эквивалентен dom, как и сам dom любому другому. И причина тут проста, «понимаемый» вами bigO имеет смысл только для сравнения разных асимптотик.

Я же уже показал O1 решение, либо вы не смогли его понять? Ну тогда объясню попроще — прямой биндинг есть O1 решение по определению. Если ещё проще, прямое изменение абстрактного value в dom-node является O1 решением.

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

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

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

Что это значит — неясно. Каким образом и из чего это следует? Я примерно предполагаю, что это какая-то смесь из нескольких популярных тезисов типа «всё дерево будет обновлять», «изменение каждой ноды(наверное имеется ввиду асинхронно) будет триггерить перерисовку, когда пакетное изменение будет триггерить одну перерисовку на пакет».

Очевидно, что первая цитата вообще несостоятельна, а вторая к vdom никакого отношения не имеет и в 10раз проще реализуется при прямом биндинге.

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

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

изменение каждой ноды(наверное имеется ввиду асинхронно) будет триггерить перерисовку, когда пакетное изменение будет триггерить одну перерисовку на пакет

Ну вы же мягкое с тёплым сравниваете. Вы увидели, что в случае observable у вас 1 какой-нибудь прицельный setAttribute, innerText = и пр., и почему-то считаете, что в этом сложность решения и состоит. Мол пришло обновление и мы одной прицельной операцией сделали всё, что хотели. Я правильно понимаю ход ваших мыслей? Но ведь вы взяли только вершину айсберга, ту что над водой. В таком разрезе оно и правда может выглядеть очень привлекательно.


Но ведь есть та, что под водой. И она может быть как крошечной (1 observable + 1 computed), так и просто громадной (какой-нибудь хитрый граф зависимостей).


В различных ситуациях разные решения будут превалировать друг над другом. В особенности это зависит от прямоты рук разработчика. В случае immutable values + vDom разработчик будет минимизировать кол-во звеньев для ререндера vDom (всякие selector-ы, правильные key, денормализация и пр.). В случае observable разработчик будет тщательно обсустраивать свой observable-граф так, чтобы оно ворочалось побыстрее. И то и другое может быть нетривиальным. Сильно зависит от задачи.


Ну а формошлёпить можно с любым подходом с закрытыми глазами.

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

А я не про вдом. Я про ваше предложение обновлять сразу по изменению биндинга.


Каким образом и из чего это следует?

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


Очевидно, что первая цитата вообще несостоятельна, а вторая к vdom никакого отношения не имеет

При чем тут вдом вообще? Я говорю про ваше О1 решение, а не про вдом.

Там не будет O(1). Там ведь трекинг зависимостей и вся машинерия по обновлению всего, что от этих зависимостей зависит. Скажем даже простейший перебор всех listeners уже O(n). А когда в дело включаются сложные переплетения различных видов observable то начинается такая кровавая баня, что порой проще утопиться, чем найти где же оно засбоило и как, откуда тут мать его race, и почему всё так тормозит. Оптимизировать реально сложный клубок бывает невыносимо "больно" )

Там не будет O(1). Там ведь трекинг зависимостей и вся машинерия по обновлению всего, что от этих зависимостей зависит. Скажем даже простейший перебор всех listeners уже O(n).

На этом уже можно закончить, т.к. человек целиком и полностью несостоятелен. Ошибок очень много, начиная с того, что человек попытался подменить понятия, ведь в ситуации с vdom(в контексте create/diff дерева) под n понимается кол-во нод в дереве, когда как в данном случае кол-во listeners.

Заканчивая тем, что он начал спорить с голосами в голове, а не со мною, откуда-то взяв какие-то observable, которые никакого отношения к теме не имеют. Прямой биндинг подразумевает объект, который имеет лишь ссылку(и) на target, т.е. те node условное value которых привязано к value упомянутого объекта.

Вот ваши слова:


У нас есть данные, которые отображаются в каких-то элементах. Нам нужно просто запомнить какие данные с чем связаны. Какие данные обновились мы знаем и далее мы просто обновляем связанные с ними элементы

Вы описали паттерн "Наблюдатель". Ваши "запомнить какие данные с чем связаны" + "Какие данные обновились мы знаем " = tracking dependencies + notify dependencies.


То что вы не знаете, как оно называется не говорит, что я как человек "несостоятелен".


т.к. человек целиком и полностью несостоятелен

А вообще как вы сюда попали? Обычно людей которые настолько кислотны тут сливают в краткие сроки. Минус в карму за повторную грубость. Продолжайте в том же духе, будете readonly.

Там не будет O(1). Там ведь трекинг зависимостей и вся машинерия по обновлению всего, что от этих зависимостей зависит.

В варианте Pappagento никакого трекинга нет, просто обновили переменную и поправился дом. Представьте себе, что у вас есть класс-модель, и на полях этой модели стоит сеттер, который обновляет связанную с этим полем ноду. Присваиваете значение полю — обновляется нода. Все. Никаких listeners, вдомов, ченж трекингов и т.д. (ну или можно считать что у вас вырожденный observable с не более чем одним listener).


Все прекрасно, О1, но проблема в том, что если вы, например, в цикле совершили на модели 1000 изменений, то это вызовет 1000 апдейтов.

Вот же его слова:


У нас есть данные, которые отображаются в каких-то элементах. Нам нужно просто запомнить какие данные с чем связаны. Какие данные обновились мы знаем и далее мы просто обновляем связанные с ними элементы

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


Но предположим я понял его превратно, и наш кислотный товарищ имел ввиду другое. Скажем если он имел ввиду, что у нас система вида каждый model.field хранит точный вбитый руками список своих dom.listeners, то это уже никакая не реактивная система. По сути примерно так писали на jQuery. Получив новое значение с какого-нибудь input мы прямо в коде прописывали все изменения, которые от этого значения должны произойти.


Все прекрасно, О1, но проблема в том, что если вы, например, в цикле совершили на модели 1000 изменений, то это вызовет 1000 апдейтов.

Ну формально можно их складировать в некую очередь, причём без повторений (скажем по ключу в хеше uniq-dom-id + attrubute-name|content|...). И выполнять уже на следующем тике. Но в целом такие ручные решения для систем сложнее простейшего формошлёпства нежизнеспособны и экономически не оправданы. Это ж ручной каторжный труд.


Вот теперь я, несостоятельный человек, правильно понял идею?


const n1 = { id: 'some-dom-id', type: 'attribute', value: fn1 };
const fn1 = () => mode.field1 + '!';
const model = {
  field1: { value: 12, notifiers: [n1, n2, n3] }
}

где n1 это тип зависимости, fn1 это вычисление самого значения, notifiers это руками вбитый список что-где-и-как обновлять, указанный для каждого поля отдельно. Ну и всё это покрыто некой либой, которое это заводит. Так?

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

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


Иначе была бы формулировка вида "прописать руками в коде".

Очевидно, что прописать может фреймворк, автоматически. Вы в темплейте указали {{ a }} — и он автоматически нагенерит нужный сеттер для переменной а класса-модели. А в коде работаете с ней просто как с обычным полем.


у нас система вида каждый model.field хранит точный вбитый руками список своих dom.listeners, то это уже никакая не реактивная система.

Почему же? Это как раз система реактивная — мы обновляем поле и с ним реактивно обновляется дом. Нереактивная система — это в реакте, где реактивных обновлений нет :)


Ну формально можно их складировать в некую очередь, причём без повторений (скажем по ключу в хеше uniq-dom-id + attrubute-name|content|...)

Можно конечно. Только теперь можно заметить, что нам в принципе не надо делать промежуточных обновлений. Зачем сохроанять всю историю, если мы последовательно сделаем все ченжи и остановимся на последнем? Можно оставить только последний чендж, сделать его — и это будет нужный нам дом-стейт. Теперь у нас в биндингах хранятся пары (старое значение, новое значение), При этом при апдейте из-за такого что информация обычно представлена иерархически (в компоненту суем список, каждый элемент списка — это объект, который суется во вложенную компоненту, так объекты во вложенных и т.д....), то при перестройке этой иерархии 99% пар у нас будут вида (х, х) — то есть значение не изменилось. Надо ли нам апдейтить неизмененные значения? Совсем не надо, это лишние траты. Лучше пройтись по всем биндингам и апдейтить только те, где значения менялись. Кроме того — если мы складируем изменения а не апдейтим каждый раз, то надо теперь как-то определять, а когда именно эти изменения надо применить. Мы же так можем 5 минут складировать и дом не апдейтить, сомнительно что пользователю будет польза от такого поведения :)
Теперь поздравляю — у вас получился чендж детекшон, прямо как в реакте или в ангуляре :)


Это ж ручной каторжный труд.

Так можно это все автоматически делать.

О, я, кажется, начал что-то понимать. Т.е. у нас, скажем, условно есть шаблон вида: <div id="1">{{ field1 }}</div>. Система автоматически выцепляет field1 и его принадлежность к div#1, и в field1.notifiers записывает это как div#1#text?


Да, такая автоматика, конечно, возможна. Правда она одноуровневая. Т.е. у нас div#1 зависит от field1, который, в свою очередь, если и зависит от чего-либо ещё, то мы это тут не рассматриваем. Хотя, на мой взгляд, это самое интересное.


Нашему кислотному товарищу не понравилось, что я сравниваю систему в комплексе, а не отдельно vDom и другие рендер подходы. Дескать, я приплетаю в этот вопрос лишнее, не относящееся к делу. Но я точно так не считаю. Ведь по сути, вот смотрите:


Говоря о react с его vDom в наиболее идиоматичном виде мы говорим об immutable-values (context, redux, state, ...) + render + vDom + reconcilation + commit. В этой цепочке у нас есть 1 важная особенность, в render мы НЕ ЗНАЕМ какие поля изменились. Мы знаем, лишь то, что что-то изменилось (SCU), ведь это plain objects (POJO). Причём измениться могло оно на вложенном уровне наших данных. И даже если мы как-нибудь (например babel-plugin) распарсим наш JSX в конструкцию вида domNode -> notifiers of particular value, то воспользоваться мы ей не сможем, т.к. мы не знаем при рендере, изменился наш particular value или нет. Мы конечно можем пробежаться по всем задействованным путям в данных, но это уже никакой не O(1).


Работа с POJO до render-а и selector-ов очень быстрая, простая, предсказуемая и т.д… Это крошечная часть под водой. А тяжёлая это как раз наш vDom. В случае если у нас сами данные реактивные (в крутом понимании этого слова), то у нас и возможностей больше. Но платим мы за это тяжёлыми обвязками и графом зависимостей, трекингом (вне render-а в данном случае) и т.д.


Ну т.е. не получается рассматривать plain objects (типовой react-way) и такой подход в отрыве от "несущественных деталей" о том, что это за данные и что мы о них знаем.


Тогда как в случае observable мы знаем более-менее, что именно у нас изменилось. И наш model.field1 может стриггерить свои dom-манипуляции. И для observable подхода мы можем построить такую цепочку обновлений.


Я не прав?

если и зависит от чего-либо ещё, то мы это тут не рассматриваем.

Если он зависит от чего-то, то мы его обновим через field1 = f(что-то). Вы, конечно, можете навернуть какую-то внутреннюю логику апдейтов вашей модели, можете через обсервабле, можете через setState(), можете редаксом обмазать. Аналогично как в реакте можете прикрутить те же обсерваблы или внутренние мутабельное двигло для моделей. Это же уже с рендерингом не связано никак вообще, так что не совсем понятно, как к обсуждению относится. У нас есть модель, когда модель меняется — мы должны обновить дом. Вот этот вот этап же (апдейт дома вслед за моделью) мы и рассматриваем. А как вы апдейтите саму модель? Ну как хотите так и обновляйте.


Работа с POJO до render-а и selector-ов очень быстрая, простая, предсказуемая и т.д… Это крошечная часть под водой.

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


Вы там выше писали про иерархию. Я вот так сходу-спрыгу не могу придумать, как можно построить такую очередь обновлений грамотно, чтобы не потерять наш O(1)

Так никак :)


Еще раз — вы либо апдейтите дом на каждый чих за О(1), либо делаете свою "очередь", которая естественным образом оптимизируется до чендждетекшона за O(n) :)

работа непосредственно внутри модели

Эмм… А какой смысл рассматривать исключительно работу внутри модели с плоской иерархией модели. Ну да, пока у нас существует только небольшой список полей с сеттером, мы в шоколаде. Но who cares? Как это связано с реальными задачами? Формочка авторизации?

Эмм… А какой смысл рассматривать исключительно работу внутри модели с плоской иерархией модели.

А что меняет "плоскость" иерархии?

Ну когда у нас всякие мутабельные массивы идут всё становится сложнее. Значения добавляются, удаляются, всякие переборы этих массивов (построение diff-ов). В общем мути много. Помню я очень давно много копался и дебажил observableArray в knockout и соответствующую часть обновлений DOM. В итоге написал свой собственный binding который в моих задачах давал ускорение на 1 десятичный порядок. В голове отложилось: эффективный рендер изменяемого массива это сложно.


Но мой посыл выше был больше иерархию вычисляемых полей. Ну что это за проект такой, где нам не нужны computed?! Всё время есть какой-нибудь граф данных, зачастую очень большой и сложный с хитрыми переплетениями. Вот я давеча был вынужден переписать проект со Vue на React + Redux только из-за странностей трекинга зависимостей во Vue. У меня шёл счёт на несколько тысяч observables и большое древо computed-полей на основе этих obersables. Из-за особенностей vue watcher.demand мне пришлось всё снести :(


А тут у нас вообще речь про простые get+set поля. Рассматриваем только передовую (единичные реактивные поля), забыв про тылы, и наслаждаемся тем, как же быстро работает setAttribute минуя vDom. Осталось только маску сварщика с головы снять и оглянуться… )

Ну когда у нас всякие мутабельные массивы идут всё становится сложнее. Значения добавляются, удаляются, всякие переборы этих массивов (построение diff-ов).

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

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

Но мой посыл выше был больше иерархию вычисляемых полей. Ну что это за проект такой, где нам не нужны computed?! Всё время есть какой-нибудь граф данных, зачастую очень большой и сложный с хитрыми переплетениями. Вот я давеча был вынужден переписать проект со Vue на React + Redux только из-за странностей трекинга зависимостей во Vue. У меня шёл счёт на несколько тысяч observables и большое древо computed-полей на основе этих obersables. Из-за особенностей vue watcher.demand мне пришлось всё снести :(

Опять рассуждения не относящиеся к теме, на которые я уже отвечал. Все эти связи существуют в нагромождениях поверх react(vdom) и будут в любом случае, если использовать этот подход.

А тут у нас вообще речь про простые get+set поля. Рассматриваем только передовую (единичные реактивные поля), забыв про тылы, и наслаждаемся тем, как же быстро работает setAttribute минуя vDom. Осталось только маску сварщика с головы снять и оглянуться… )

Любая нода это и есть get/set и больше ничего. А все ваши попытки говорить о чём-то ещё — это попытки уйти с обсуждаемой темы, на тему проблем дефолтных подходов. Т.е. говорить о теме в доступных(привычных) вам понятиях, т.к. за рамками оных вы теряетесь.

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

Ну вот в реакте нету computed и прекрасно живут люди с этим.

Кто живёт? Как живёт? Люди цепляют redux-ы, селекторы, mobx-ы и т.д. React-приложение без подобных штук либо предельно простое, либо его не существует в природе. Ну либо это какой-то лютый неподдерживаемый страх господень.


Вот на примере распространённой связки react + redux + reselect (и др. мемоизация): Селекторы = те самые computed. Строятся деревьями. Без них (или аналогов) нормальное больше приложение написать нельзя. К тому же считается крайне рекомендуемым подход с нормализацией данных, так что мем-селекторы просто must-have.


В случае mobx, rxjs и других реактивных подходов — там совсем всё очевидно.


Просто React выступает как остов для компонент и работа с DOM. Само приложение находится за пределами React-а. И при этом React работает с POJO. Т.е. требований к этому приложению вне React-а куда меньше. Но из-за этого никаких прицельных обновлений сделать нельзя.

Кто живёт? Как живёт? Люди цепляют redux-ы, селекторы, mobx-ы и т.д. React-приложение без подобных штук либо предельно простое, либо его не существует в природе.

Ну так вот во всех случаях, кроме мобх, нету никаких computed.


Селекторы = те самые computed.

А, ну так можете тогда просто любые ф-и объявить cmomputed и все.


Без них (или аналогов) нормальное больше приложение написать нельзя.

Не надо просто пихать все в глобальный стейт, тогда и проблем не будет :)

А, ну так можете тогда просто любые ф-и объявить cmomputed и все.

Могу. Главное что они есть. Они тяжёлые. И их нужно кешировать. Можно не кешировать, но будет тормозить. В случае observable кеширование из коробки. В случае мемоизаторов — тоже. Если голые функции — ну вы ССЗБ. Суть да — это вычисляемые поля. Часто образуются деревья вычислений + мемоизация.


Не надо просто пихать все в глобальный стейт, тогда и проблем не будет :)

Будет. И не важно глобальный он или нет. Тут главное, что стейт есть. И приложение от него зависит. Стейт меняется. Приложение должно адаптироваться под изменения. Взаимосвязи и хитросплетения сложные. Ну и т.д..


В общем шаг влево шаг в право от всяких формошлёпств (скажем большой редактор, или какой-нибудь excel) — и вы никуда не денетесь с подводной лодки.


А форму логина можно написать и без "этих ваших реактов" )

Могу. Главное что они есть. Они тяжёлые. И их нужно кешировать. Можно не кешировать, но будет тормозить. В случае observable кеширование из коробки. В случае мемоизаторов — тоже. Если голые функции — ну вы ССЗБ. Суть да — это вычисляемые поля. Часто образуются деревья вычислений + мемоизация.

Это все прекрасно, но при чем тут рендеринг и вообще V из MVC?


Будет. И не важно глобальный он или нет. Тут главное, что стейт есть.

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


В общем шаг влево шаг в право от всяких формошлёпств (скажем большой редактор, или какой-нибудь excel) — и вы никуда не денетесь с подводной лодки.

Если нужен какой-нибудь excel, то это уж не для реакта кейз. Для кровавого ынтырпрайза со сложной логикой, нетривиальным уи, и такое прочее есть ангуляр. Реакт — это больше про ленту твитов и вот это вот все :)

Это все прекрасно, но при чем тут рендеринг и вообще V из MVC?

Я же выше описал. В случае observable мы можем дёрнуть те самые get-set значения. В случае POJO не можем. Применимость vDom для true-реактивных значений под вопросом (можно сильно прогадать). Применимость обсуждаемого решения для immutable pojo ― просто невозможна.


Грубо говоря — ну не получается обсуждать это в отрыве. Мы не можем вот так вот взять оторвать V здесь и сказать — смотрите какой vDom плохой. Это совсем не конструктивно.


Т.е. не просто причём — а по сути самый главный вопрос в этом и состоит.


Если нужен какой-нибудь excel, то это уж не для реакта кейз

Ха-ха. Ок :)

> В случае observable мы можем дёрнуть те самые get-set значения. В случае POJO не можем.

Ну а рендер тут при чем?

> Грубо говоря — ну не получается обсуждать это в отрыве. Мы не можем вот так вот взять оторвать V здесь и сказать — смотрите какой vDom плохой.

Эм… почему не можем?

> Т.е. не просто причём — а по сути самый главный вопрос в этом и состоит.

Я все еще не могу понять, где связь. Вы четко можете ответ на этот вопрос сформулировать?
Ну а рендер тут при чем?

Render будет тогда и только тогда, когда мы дёрнем setter. А setter мы никогда не дёрнем, ибо нечем нам его дёргать. В итоге рендера нет. Либо дёргаем все сеттеры всегда… Получаем ерунду.


Вы четко можете ответ на этот вопрос сформулировать?

Боюсь, что более понятно, чем комментарием ниже не могу.

Попробую более понятно. Скажем у нас есть фреймворк работающий по обсуждаемой схеме. Статичная структура дом, плоская структура полей в модели (view-model). И ежу понятно, что мало-мальски сложное приложение так не построить. Но, начинаем рассматривать это как отдельный V-слой и игнорируем откуда и как к нам приходят данные. По сути у нас к внешней оболочке приложения только 1 требование: нам нужно чтобы оно дёргало сеттеры наших полей модели.


И теперь мы встаём перед вопросом. Как этого добиться?


В случае observable решений всё просто — subscribe-ся от конкретных значений, которые нас интересуют. Что-то вроде маппинга конкретных observable к конкретным setter-ам из V. Делается относительно тривиально. Поменялось поле в реальной модели -> observable.notify -> setter -> setAttribute. Ляпота.


А теперь у нас подход ака functional-like. POJO объекты и ссылочная целостность. Нам нужно дёрнуть изменённое поле? Откуда нам знать какое поле изменилось? А не откуда. Только пробежаться по всем полям и проверить. Т.е. к слою V мы добавляем ещё 1 слой V2, который будет держать руками вбитые привязки и актуализировать значения через сеттеры. Ну в общем что попало. Ещё пойди извернись и сделай это автоматически, не написав свой собственный observable.


При этом подход с obserable тяжёл до рендера, а с immutalbe pojo во время.


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

Статичная структура дом, плоская структура полей в модели (view-model).

Почему плоская-то?


В случае observable решений всё просто — subscribe-ся от конкретных значений, которые нас интересуют.

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


Нам нужно дёрнуть изменённое поле? Откуда нам знать какое поле изменилось?

Так на поле сеттер стоит. Он и дернет все, что нужно.

Druu вы меня не понимаете. Или я вас. И похоже совсем. Какой setter? Куда добавляем? Откуда он возьмётся? Кто его дёрнет? О чём вы? )


Вот есть у нас V. У неё есть 10 полей. Т.е. 10 setter-ов. Есть у нас приложение, со своей бизнес-логикой. Работает по схеме immutable pojos (скажем мы сюда redux запихали или что-нибудь типа elm). Приложение автоматически определяет, что наш V должен обновиться, т.к. что-то из его зависимостей изменилось. Что именно — не известно. Какой setter из 10 будем дёргать? Все? Делать full comparison, а потом дёргать?


Это же сама парадигма рендера другая в vDom. Мы не знаем что изменилось и знать не хотим, позволяя внешнему приложению, работающему с исходными данными, ничего не трекать. Мы просто используем V как pure-function от данных. Никаких заморочек по определению какие именно данные изменились. Просто гоняем туда сюда immutable объекты. И за счёт многочисленных сравнений накатываем изменения на реальный DOM. Такая вот парадигма рендера. Да всё вверх-ногами, да необычно. Но именно так.


И ну это просто бессмысленно рассматривать и критиковать такую схему работу с данными и V в отрыве друг от друга. В таком случае vDom всегда будет слабым звеном, которое ну разве что на свалку.


Боюсь ещё подробнее я описать это не в состоянии )

Какой setter из 10 будем дёргать? Все? Делать full comparison, а потом дёргать?

В каждом из десяти сеттеров уже есть весь апдейт.Ничего дополнительно дергать не надо.


Это же сама парадигма рендера другая в vDom.

Ну с vdom да. Но мы же не про vdom?


И ну это просто бессмысленно рассматривать и критиковать такую схему работу с данными и V в отрыве друг от друга.

Так смысл разделения на вид и модель именно в том и состоит, чтобы их можно было рассматривать по отдельности.

В каждом из десяти сеттеров уже есть весь апдейт.Ничего дополнительно дергать не надо.

Не понял. Ничего там нет. Надо явным образом его вызывать с новым значением.


Ну с vdom да. Но мы же не про vdom?

Мы тут про vDom vs "o(1) get-set".


Так смысл разделения на вид и модель именно в том и состоит, чтобы их можно было рассматривать по отдельности.

До каких-то пределов. В случае React на входе можно подать любые данные. В случае… как бы это назвать, нашего V:O(1), входных данных вообще по сути нет. Есть setter-ы которые надо вызывать "когда нужно". При этом "когда нужно" решает не V. Годится для observable (они знают "когда нужно").


Либо я, либо вы, либо мы оба, держим в голове АБСОЛЮТНО разную ментальную картинку происходящего. Как на разных языках говорим.

Мы тут про vDom vs "o(1) get-set".

Ну вот в "o(1) get-set" у вас все есть в геттерах сеттерах, конец истории. Нет?


Либо я, либо вы, либо мы оба, держим в голове АБСОЛЮТНО разную ментальную картинку происходящего.

У меня тоже такое ощущение :)


вы какой вообще тезис пытаетесь сформулировать?

Ну вот в "o(1) get-set" у вас все есть в геттерах сеттерах, конец истории. Нет?

Нет, там будет только то, что мы туда положим. Ничего не положим, ничего не будет.


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


Вот родит наш кислотный товарищ готовую библиотеку, тогда и продолжим, ок? )

Правда с приходом нового иммутабельного контекста, новых точечных useState хуков и примитивов для мемоизации из коробки в react, мы можем обойтись стандартными возможностями либы. Но используя мемоизацию мы построим всё те же computed-ы, просто называться они будут по-другому.

Еще раз — вы либо апдейтите дом на каждый чих за О(1), либо делаете свою "очередь", которая естественным образом оптимизируется до чендждетекшона за O(n) :)

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

Зачем сохроанять всю историю, если мы последовательно сделаем все ченжи и остановимся на последнем?

Условно говоря, несмотря на то, что setAttribute крайне быстр, значение для setAttribute может быть гхм… более тяжёлым. Ну так себе случай, но всё же :)


Вы там выше писали про иерархию. Я вот так сходу-спрыгу не могу придумать, как можно построить такую очередь обновлений грамотно, чтобы не потерять наш O(1) в случае ветвлений в иерархии DOM (всякие if, else, switch, ...). По сути мы не можем в произвольном порядке накатить произвольное изменение, т.к. нужной domNode может вовсе не оказаться ещё, т.к. её изменение на её создание стоит в очереди глубже. Тут надо думать… Все мои мысли приводят к циклам и падению асимптотики )

Вы там выше писали про иерархию. Я вот так сходу-спрыгу не могу придумать, как можно построить такую очередь обновлений грамотно, чтобы не потерять наш O(1) в случае ветвлений в иерархии DOM (всякие if, else, switch, ...). По сути мы не можем в произвольном порядке накатить произвольное изменение, т.к. нужной domNode может вовсе не оказаться ещё, т.к. её изменение на её создание стоит в очереди глубже. Тут надо думать… Все мои мысли приводят к циклам и падению асимптотики )


Спустя столько времени до вас наконец-то дошло(а возможно вы прочитали) проблема структуры. Только сообщаю вам, что никакая произвольная структура не нужна и эта та самая дыра в вашем понимании.

Структура вся заранее описана, структура изменяется только в очень редких случаях — 90% кейсов это массивы. Очевидно, что любая операция над тем же массивом знает то, как она его изменяет и таким образом может изменить dom-отображение.

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

Опять подмена понятий. Автоматизированный — это не только известный вам примитивный паттерн.

Вот теперь я, несостоятельный человек, правильно понял идею?

Слишком много лишнего в понимании.

id: 'some-dom-id'

Фундаментальный проблема номер раз — хтмл-мышление, либо текст-мышление.

notifiers это руками вбитый список

Фундаментальный проблема номер два — скриптуха-мышление. Проявляется это в непонимании различий между структурой и лапшой. Структура уже существует в коде, её не нужно никак отслеживать и прочее.

codepen.io/anon/pen/yGXOjM — минимальный пример.

В варианте Pappagento никакого трекинга нет, просто обновили переменную и поправился дом. Представьте себе, что у вас есть класс-модель, и на полях этой модели стоит сеттер, который обновляет связанную с этим полем ноду. Присваиваете значение полю — обновляется нода. Все. Никаких listeners, вдомов, ченж трекингов и т.д. (ну или можно считать что у вас вырожденный observable с не более чем одним listener).

Это бесполезно, но раз кто-то понял — я объясню нюансы. Мне лень объяснить их тем, кто целиком и полностью ничего не понимает и понимать не желает.

В данном схеме существует одна проблема, фундаментальная проблема, которую и решает vdom(на халяву) — это структура. Дело в том, что в базовом подходе данные не просто обновляют, а изменяют структуру. Тем самым может появиться значение, которое вообще ни с чем не связано.

Да, это решается построением объектов, которые сами всем этим управляют и проблем нет, но в дефолтной(обобщённой) ситуации это проблема. И если и пытаться критиковать этот подход, то именно с этой стороны.

Расскажу ещё немного про решение. Дело в том, что в дефолтном подходе некий хтмл(т.е. структура некоего поддерева) попросту захардкорена, а даже если мы использует так называемый «компонентный подход», то это мало что меняет. В конечном итоге нижняя структура первичная, а верхняя вторична.

Именно это и создаёт проблемы, но решение есть — это строить структуры данных таким образом, что они и будут представлять конечную структуру, а уже далее это будет преобразовано в ту самую dom-структуру.

По-сути реакт(и vdom в частности) и является чем-то подобным, но плохой он потому, что он экспортирует наверх структуру «под», т.е. ненужную нам структуру dom, когда мы работаем со структурами данных. Именно с преобразованием структура данных -> структура dom и возникают проблемы.

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

Допустим, если мы делаем push(value), то мы точно знаем, что нам нужно создать ноду и добавить её как child другой ноде, которая и представляет наш array, которая напрямую связана с array.

Тот же vdom этого сделать не может потому, что там произвольная структура(представляющая структуру dom, а не наши данные), да и вообще это несостоятельное immutable.

Все прекрасно, О1, но проблема в том, что если вы, например, в цикле совершили на модели 1000 изменений, то это вызовет 1000 апдейтов.

Проблема целиком и полностью надуманная. Но даже она имеет элементарное решение — ввод инварианта(в рамках описанного мною выше решения) одно обновление на ноду.

Если изменения будут изменять структуру, то в самом примитивном варианте туда без проблем впиливается diff. Более вменяемые решения реализуются уже для конкретных структур данных и в рамках их логики.
Но даже она имеет элементарное решение — ввод инварианта(в рамках описанного мною выше решения) одно обновление на ноду.

Какое из тысячи?
Вот я написал a = 1, a = 2, a = 3, a = 4, и у меня биндинг на а, с-но я а изменил 4 раза. Какое из изменений должно отработать?


Если изменения будут изменять структуру, то в самом примитивном варианте туда без проблем впиливается diff.

И как только вы ввели дифф у вас получился реакто/ангуляр.

Какое из тысячи?

Очевидно, что последние.

Вот я написал a = 1, a = 2, a = 3, a = 4, и у меня биндинг на а, с-но я а изменил 4 раза. Какое из изменений должно отработать?

Код не имеет смысла, проблема не имеет смысла.

И как только вы ввели дифф у вас получился реакто/ангуляр.

Неверно. Здесь нет произвольной структуры, здесь нет структуры пляшущей от dom и нет всех этих проблем и тормозов.

К тому же, почему вдруг обновление ноды на каждый чих вообще стало является проблемой? Проблемой является перерисовка, а не обновление.

Очевидно, что последние.

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


Неверно.

Как же неверно, если описанная вами конструкция — это в точности то, что используется в современных фреймворках?


Здесь нет произвольной структуры, здесь нет структуры пляшущей от dom и нет всех этих проблем и тормозов.

Дык и в ангуляро-реакте ее нет. Там есть структура которая содержит новое состояние биндингов, та же структура, что и у вас. Можете назвать ее виртуальный дом. Домом она, конечно, не является и вообще дому в том или ином смысле соответствовать не должна. Единственное ее предназначение — узнать, что изменилось с прошлого раза (какие биндинги заапдейтились).


К тому же, почему вдруг обновление ноды на каждый чих вообще стало является проблемой? Проблемой является перерисовка, а не обновление.

Поскольку обновление ведет к перерисовке — оно стало проблемой, да.

Поскольку обновление ведет к перерисовке — оно стало проблемой, да.

Не обязательно. Смотрите:


const domNode = ...();
domNode.innerText = '1';
domNode.innerText = '2';
domNode.innerText = '3';

Сколько было настоящих перерисовок (reflow)? 1-а. Когда она произошла? Условно — на следующий тик. Почему? Ну браузер ждёт пока JS прекратит свои издевательства и только потом делает reflow.


Ну и можно всю малину испортить, если где-нибудь посередине дёрнуть что-то типа domNode.offsetWidth. Тогда будет досрочный reflow.

Дык и в ангуляро-реакте ее нет. Там есть структура которая содержит новое состояние биндингов, та же структура, что и у вас

Вы не могли бы пояснить, что вы имеете ввиду на примере react? Там ведь как раз структура, которая пляшет от dom (только ненастоящего браузерного, а супер-облегчённого pojo). И там не хранятся состояния байндингов, нет. Там хранится всё. Ну словно у нас есть в шаблоне 5 нод, и из динамики 1 innerText и 1 setAttribute. При том что реальных аттрибутов и пр. (включая статичные) там в 10 раз больше. Так вот, react будет хранить ВСЁ ЦЕЛИКОМ, а не два наших байндинга. Такие дела :)

Вы не могли бы пояснить, что вы имеете ввиду на примере react? Там ведь как раз структура, которая пляшет от dom (только ненастоящего браузерного, а супер-облегчённого pojo). И там не хранятся состояния байндингов, нет.

Это просто способ представления, который показался удобен разработчикам для реализации диффа. По факту, от вдома не требуется ничего, кроме фиксации биндингов.

По факту, от вдома не требуется ничего, кроме фиксации биндингов.

Согласен. Но только при условии что хотя бы сам шаблон статичен. Какой был при первом рендере, такой и остался. Но это не про React, конечно. Там хоть random вытворяй.

На чём основаны эти заявления?

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


Ну тогда объясню попроще — прямой биндинг есть O1 решение по определению. Если ещё проще, прямое изменение абстрактного value в dom-node является O1 решением.

Надо рассматривать систему в комплексе. Судя по вашему описанию выше, вы описали observable подход. См. скажем, knockoutjs. А у него асимптотика "render"-а зависит не от количества dom-нод, а от сложности графа зависимостей. И очень сильно от реализации трекинга зависимостей. Т.е. условно, при vDom у вас асимптотика O(n), а при observable aka knockoutjs у вас O(m). И разные константы при этом. Константа у observable большая, и сама структура, обеспечивающая такую работу — тоже.


Т.е. если попроще: несмотря на то, что какой-нибудь knockoutjs может прицельно поменять какой-нибудь аттрибут dom-ноды в зависимости от какого-нибудь computed-а, а не строить кусок псевдо-древа и потом его реконсилировать, делать такую "простую" задачу он может как дольше, так и быстрее. И это сильно зависит от реальной зависимости этого computed-а. И от трекинга зависимостей. И от прямоты рук разработчика.


Возможно вы очень плохо понимаете как они работают. Если это так, то попробуйте реализовать простейший observable-примитив самостоятельно. Довольно быстро увидите, что O(1) там и не пахнет.


P.S. ничего против "паттерна наблюдатель" не имею. На нём основано множество библиотек и фреймворков. Скажем тот же redux — это observable. Vue — observable, KnockoutJS — observable. В каком-то виде observable сквозит из каждой щели, это удобный примитив. Вопрос лишь в том, является ли он у вас главным кирпичиком в системе, или нет. И касательно рендера, некоторые библиотеки с observable, к примеру Vue, несмотря на observable используют именно virtualDOM для рендера. Т.е. они целенаправленно отказались от подхода описанного выше. Вероятно сделав замеры посчитали vDom более быстрой альтернативой. Кто знает — я не копал в эту сторону.


И последнее. Не стоит писать в таком агрессивном стиле. Мы не на lor-е.

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

Я имею как минимум не меньшее представление, а про магию вам никто не говорил.

Надо рассматривать систему в комплексе.

Неверно. vdom не является комплексом.

Судя по вашему описанию выше, вы описали observable подход.

Неверно.

См. скажем, knockoutjs. А у него асимптотика «render»-а зависит не от количества dom-нод, а от сложности графа зависимостей.

Опять подмена понятий. Все графы зависимостей там существуют между данными, которые никакого отношения к render не имеют, и такие же зависимости будет с vdom и без него.

Никаких графов между dom-node и ассоциированным с ним значением/объектом — нет. И именно тут происходит подмена понятий, а именно рассуждения на тему «как связано это конечное значение и неким абстрактным состоянием приложения». Всё это не имеет отношения к делу и vdom тут никак не помогает.

И очень сильно от реализации трекинга зависимостей. Т.е. условно, при vDom у вас асимптотика O(n), а при observable aka knockoutjs у вас O(m).

Как это удобно, манипулировать а потом забывать, делая вид, что ничего не было. А ведь и там и там было O(n).

И разные константы при этом. Константа у observable большая, и сама структура, обеспечивающая такую работу — тоже.

Дак констант же нет? А теперь уже есть. И да, с чего вдруг она большая? Опять какие-то нелепые тезисы.

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

Поэтому я вижу только одно решение — выпилить и забыть о несостоятельной декларативной концепции. И тут наши взгляды расходятся и я вам никак не помогу. Это значит, что я считаю подход jsx куда более вменяемым, нежели любые признаки текстовых шаблонов. А т.к. текстовые шаблоны несостоятельны, то все они императивны и по-сути имплементируют какой-то имеративный недоязык в рамках себя. Соответственно, любой из этих недоязыков является мене состоятельным через язык реальный, а значит язык реальный куда более подход для выражения всего того, что выражается через этот недоязык.

Соответственно, любой из этих недоязыков является мене состоятельным через язык реальный

С чего бы вдруг? Специально заточенный под задачу язык, очевидно, более удобен и эффективен для решения этой задачи, чем язык общего назначения. Точно так же, как ЯП более эффективен для программировании, чем универсальный ЕЯ.

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

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

А специально заточенным языком в общем случае будет является api на том самом языке. Да, js очень слаб для этого, но это проблема чисто js.
Правильно, а в данном случае этим самым специально заточенным языков — является язык общего назначения.

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


Да, js очень слаб для этого, но это проблема чисто js.

А какой язык не слаб?

Зарегистрируйтесь на Хабре, чтобы оставить комментарий