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

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

возникает проблема излишней свободы

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



Самое главное приемущество у MobX по мимо того, что он использует getters/setter для автоматической подписки/отписки на изменения это его свобода. А вы выдаёте его главное достоинство за недостаток.
Я думаю вам с такой логикой нужно вообще в другую сторону пойти, в сторону Angular, вот там вам руки свяжут по швам конкретно и будете радоваться жизни в отсутствии свободы. Там и DI как вы любите и много прочей нечисти.

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

Самому мне нравится свобода MobX, потому я, как мне кажется, оставил достаточное место для маневра в описываемом подходе. Однако, для тех людей, кто считает, что свободы слишком много, я описал несколько простых правил

Однако, если вы читали мою предыдущую статью, о чем я просил в начале

Читал

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

Да? Правда? Кроме слова "недостаток" и каких-то выдуманных кейсов не имеющих отношения к реальности и ни одного реального недостатка не видел и не встречал за 6 лет использования связки React + MobX во множестве проектов. Возможно потому что и реальных недостатков то и нет? Кривые руки разработчиков к недостаткам технологии X, Y и т.п. относить нельзя, это сугубо недостатки человеческого характера.

Вот посмотрите как всё элементарно, и не загрязнено DI и т.п.
https://codesandbox.io/s/green-moon-vujxxv?file=/src/App.tsx

Кажется, вы не понимаете мою мысль. Я люблю MobX и считаю его замечательным инструментом. А свой подход я лишь показал для тех людей, которые его считают не самым удобным. Таких людей много. Погуглите статьи, сравнивающие MobX и Redux, и вы удивитесь, тому как часто всплывает тезис "В MobX слишком много свободы".

Вы считаете, что MobX идеален и это ваша правда. Они считает, что в нем есть недостатки. И это их правда. Мир не делится на черное и белое. И опять же повторюсь, моя статья направлена как раз на таких людей.

и вы удивитесь, тому как часто всплывает тезис "В MobX слишком много свободы".

И что? Пусть идут лесом, т.к. это пустые слова. Тот кто реально так считает, скорее всего и не разработчик вовсе, а тот кто пришел в профессию исключительно из-за зарплаты. Разработка это целое творчество, а не работа за станком где задача А решается только одним спобсбом и никак иначе.

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

Так им пофиг, у них либо Redux(и/или его производные) головного мозга, либо Angular/RxJS головного мозга, эти болезни неизлечимы.

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

А смысл? Вы просто подаете очень плохой пример как работать с MobX и таким образом вводите в заблуждение нормальных разработчиков. Зачем-то усложняете элементарные и очевидные вещи на ровном месте.

Значит в конечном итоге вы считаете, что описываемый мной подход плох. С этого и стоило начинать. Вы бы не могли описать, чем он плох?

Кстати, странно рассуждать о незагрязненности каким-либо паттерном в коде из пары десятков строк кода. Все-таки паттерны нужны для упрощения разработки на больших масштабах. В проектах из 100+ тысяч строк кода, совсем уж без паттернов не обойтись. Может, конечно, они будут самописанными и не будут общепризнанными как MVVM или DI, однако они будут. Потому что иначе кодовая база превратится в нечитаемое сложно поддерживаемое мессиво. Однако, это уже разговор о необходимости паттернов, что мало относится к статье

Так весь ваш описанный подход сводится к тому, как в компоненте получить состояние MobX'овского класса, просто в извращенном виде. А я просто показал как это делается если вы не относитесь к числу программистов-извращенцев)

В огромных проектах, я точно так же просто делаю import состояния и читаю его в компонентах, а если в проекте будет 1 billion lines of code, то в этом плане всё равно ничего не изменится, import { someState } from 'lalal' и вперед.

Тут же сразу отвечу на ваш коммент:

Значит в конечном итоге вы считаете, что описываемый мной подход плох. С этого и стоило начинать. Вы бы не могли описать, чем он плох?


Напишите в ответе код компонента который работает с 3мя глобальными состояниями и с одним своим локальным. Как будет выглядеть ваш
view(OtherPageViewModal)(({ viewModel }) => ( и хак в стиле создать специальный класс, который с агрегирует все эти состояния в себе не применять, т.к. он дополнительно убивает самое главное правило кода - наглядность и очевидность.

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

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

Далее цитата из моего предыдущего коммента:

Напишите в ответе код компонента который работает с 3мя глобальными состояниями и с одним своим локальным. Как будет выглядеть вашview(OtherPageViewModal)(({ viewModel }) => ( и хак в стиле создать специальный класс, который с агрегирует все эти состояния в себе не применять

Формулировка слишком расплывчатая, но да ладно.

import { injectable } from 'tsyringe';
import { computed, makeObservable } from 'mobx';
import { view, ViewModel } from '@yoskutik/react-vvm';
import { SecurityService, LicenseService, LICENSE_UPDATE } from '@services';
import { UserStore } from './UserStore';

@injectable()
class OtherPageViewModal extends ViewModel {
  // Допустим, это локальное состояние, лень придумывать
  @computed get name(): string {
    return this.user.isLogged ? this.user.username : 'Странник';
  }
  
  constructor(
    private user: UserStore,
    public security: SecurityService,
    private license: LicenseService,
  ) {
    super();
    makeObservable(this);
  }

  onLicenseUpdateClick = () => {
    this.license.update();
  }
}

const OtherPage = view(OtherPageViewModal)(({ viewModel }) => (
  <div>
    <h2>Other Page</h2>
    <h3>
      {`Привет, ${viewModel.name}!`}
    </h3>
    <span>
      Ваш номер лицензии:
      {viewModel.license.number}
    </span>

    {viewModel.security.isAllowed(LICENSE_UPDATE) && (
      <button onClick={viewModel.onLicenseUpdateClick}>
        Обновить лицензию
      </button>
    )}
  </div>
));

Я же написал

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

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

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

А теперь взгляните на мой код, который я показывал выше. И увидите разницу, почему ваш вариант плохой.

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

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

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

Вы сейчас говорите о субъективных понятиях. Я считаю, что код очень хорошо читается. По мне так в разы лучше чем ваш, в котором, кстати, нет никаких 3 глобальных состояний и одного локального.

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

Думаю наш спор никуда нас не заведет. Вы можете считать, что лучше писать код полотном. Ну и пожалуйста. Я же считаю, что эта практика деструктивна.

ваш, в котором, кстати, нет никаких 3 глобальных состояний и одного локального.

Правда что ли? Вот стрелочки, смотрите, так видно? Или всё ещё состояний нет?

А че, так можно было что ли?)
А че, так можно было что ли?)

Я считаю, что код очень хорошо читается. По мне так в разы лучше чем ваш

Т.е. бегать по цепочкам файлов, папок и бесконечных абстракций, чтобы понять что именно читает/вызывает/и т.п. компонент это в разы лучше, чем понимать это сразу же?

Операция "залезть в класс обертку" занимает меньше полу секунды с IDE.

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

Да, про 3 состояния все же не прав, признаю.

Ну опять же. 2 файла по 400 строчек или один на 800 - пожалуйста, считайте, что лучше 800 строчек в одном и страшитесь абстракций. Ваши доводы меня, к сожалению, переубедить не смогли

Ну опять же. 2 файла по 400 строчек или один на 800

При чем тут кол-во строк и файлов, и вообще code splitting, если все крутится вокруг того, как компонент получает стейты. Речь про это, а не про 800 строк vs 400 строк. Вы используете DI и промежуточный класс агрегатор, опять же просто так, по приколу. А я просто делаю Import и использую в явном виде.

Да, про 3 состояния все же не прав, признаю.

Их 4, 3 глобальных, 1 локальное. И главное всё в явном виде, сразу понятно какое глобальное, а какое локальное.

Постеснялись бы код с глобальными переменными на публике показывать. Это делает ваши компоненты не переиспользуемыми.

Постеснялись бы код с глобальными переменными на публике показывать. Это делает ваши компоненты не переиспользуемыми.

Да тут нечего стесняться, я же реалист) Я не борюсь с выдуманными, преувеличенными или высосанными из пальца "проблемами")

1) Кто сказал что судьба каждого компонента быть переиспользованным? Это участь лишь малой части компонентов в проекте.
2) Кто сказал что компонент который используется большем чем в одном месте не может читать глобальное состояние приложения?

P.S. Тут речь про разработку web приложения, а не UI кита, который точно ничего не должен знать о глобальном состоянии.

Пока вы оправдываете свой говнокод, я переиспользую целые приложения. Тут, например, в приложение $hyoo_mol встроено $hyoo_js_perf для бенчмаркинга, а в него встроено $hyoo_js_eval для отладки кейса. И для встраивания мне не потребовалось даже вносить в них изменения.

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

Разработка это целое творчество, а не работа за станком

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

В огромных проектах, я точно так же просто делаю import состояния и читаю его в компонентах

Судя по тому, что Вы так агрессивно предлагаете, Вы назначили себя опытным "не-джуном" сами и в признании извне не нуждаетесь:) Любопытно (на самом деле нет), как вы тестируете потом такой код. Хотя допускаю, что Вы за 6 лет работы с React и MobX могли не увидеть в этом необходимости.

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

Спасибо за материал. Чувствуется глубокое погружение в тему. Почему не используете MST? Да, там болтливый синтаксис описания сторов. Но озвученная вами проблема совместного использования данных решена. До кучи — time travel и снапшоты из коробки — легко подключать API.

Почему не используете MST?

Наверное что бы НЕ убить всю прелесть MobX'a, чтобы НЕ убить производительность, чтобы НЕ убить код, чтобы приложение не упало если в MST ждём например number, а с бэка пришел string или не пришло поле вовсе, и т.д и т.п.

Да, там болтливый синтаксис описания сторов. 

Вот и нафиг оно не упало, как бы Typescript есть.

До кучи — time travel и снапшоты из коробки

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

Чувствуется глубокое погружение в тему.

Ну как сказать)) Больше похоже на услышал звон, не знаю где он)

@markelov69, конечно, весьма резко выразился. Однако, я не могу с ним не согласиться. Связка MobX и TypeScript покрывает большинство реальных потребностей, которые постарался закрыть MobX State Tree.

Необходимость в time travel для меня стоит под сомнением. Особенно, когда у вас несколько десятков, а то и сотен сторов. Мне кажется, разработчики MST просто хотели реализовать фичу, которая была возможна в Redux. Однако, в MobX она мне полезной не кажется.

А снапшоты мне кажутся слишком неутилитарной функциональностью. Будто далеко не всем они в принципе могут пригодиться.

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

MST не имеет смысла, если не нужен time travel (сайтики). Но сложно представить себе приложения, вроде google docs, mirro или figma, где time travel был бы отломан. Пользователи не поймут. Поэтому в первую очередь, выбор инструмента зависит от задачи. У меня есть опыт рефакторинга https://singularity-app.ru/, где cmd+z — must have и уже была реализована на redux.

Ощущения от MST такие:

Первые: "бля, что за черная магия вне Хогвардса?". После редакса код получался слишком простым. Ты просто пишешь логику приложения, а не все эти сраные экшин-криэйторы. Как так то? В общем, программисты ненавидят черную магию. По крайней мере, пока в ней не разберутся. Почитав исходники — стало понятно, как это все устроено. В прочем, авторы MobX и MST нифига не эстеты по коду, тут им не респект.

Вторые. Ладно, с черной магией разобрались. Что там со скоростью? Были сомнения, что на большом количестве сторов с большим объемом данных, производительность можно будет обнять и плакать. Но нет. Реальные замеры показали что у нас все ОК. И хотя MST чего-то там делает, требует каких-то накладных расходов (по сравнению с редаксом), но это все — копейки. По итогу рефакторинга, скорость только росла. Пользователи заметили. В прочем, я связываю это не с MST, а с рефакторингом селекторов и мидлварей. Запишем, что со скоростью все ОК.

Теперь о минусах. Их было три. И все они были на поверхности.

  1. Болтливое описание типов в сторах. "Ну блин, нахрена ж вы так сделали?". Авторам нет оправдания :)) Не, я понимаю, почему они сделали именно так. Но это не круто. В идеальном мире я хочу просто перенаследовать модели с бэкэнда и сделать их обсерверными. Но MST говорит "обрыбишься".

  2. Наследование сторов через трамбу-лямбу (композицию). Блевотка же. Этот минус следует из первого. На простых приложениях это не потребуется. Но если много actions / views — распилить стор по слоям очень захочется. MST по сути предлагает решение. Но оно не эстетичное.

  3. Асинхронные экшины через генераторы. Вот за это хочется бить палками. Благо, таких операций у нас получилось всего 4-5 (CRUD) и их аккуратно спрятали с глаз долой в базовую модель. Но смотреть на flow(function* fetchProjects()... у меня глаз дергается. Могли бы напрячься, и починить обычные промисы/асинк/авэйт.

Что запишу в плюсы (они все спорные, но для меня — плюсы, поэтому кто не согласен — просто отвалите):

  1. Код реально получается ОЧЕНЬ простой. Как и архитектура. И кода становится в разы меньше, чем на redux. Меньше кода — меньше багов. Разработка ускорилась.

  2. Тестируемость. По сути, в тестах мы замокали один объект (backend-connector), передаваемый в mst через депенденси-энджекшин. И получили простые, чистые, красивые юнит-тесты на селекторы и экшины. Я — доволен.

  3. Таймтревел и снапшоты. В нашем случае это must have. Порадовала работа с патчами.

Чем не стали пользоваться: в MST есть коннектор к Redux-стору. По сути — тупая мидлварь на два экрана. Были мыслишки, что для рефакторинга это будет удобно. По факту удобнее оказалось извлекать какю-то сущьность из редакса целиком.

Итого: если тайм тревел не нужен — вам не нужен MST. Если нужен — на чистом MobX его рехнешься писать. Ну а redux, безусловно, нужно потихоньку хоронить.

Но сложно представить себе приложения, вроде google docs, mirro или figma, где time travel был бы отломан. Пользователи не поймут. 

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

Могли бы напрячься, и починить обычные промисы/асинк/авэйт.

На уровне js это не починить. Тут надо спецификацию языка менять.

И получили простые, чистые, красивые юнит-тесты на селекторы и экшины. Я — доволен.

То есть пользовательские сценарии у вас не тестируются, ясно. Геттеры и сеттеры тестировать, конечно, проще, но смысла в этом не очень много.

если тайм тревел не нужен — вам не нужен MST. Если нужен — на чистом MobX его рехнешься писать. 

Обсервабл с деревом и линзы с компутедами - ну строчек на 70 выйдет. Рехнуться можно, да. Но если говорить всё же не про таймтревел, а про откаты/накаты, то я бы рекомендовал такой подход, где отслеживаются лишь действия текущего пользователя, лишь в наблюдаемых свойствах, лишь наблюдаемых объектов.

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

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

То есть пользовательские сценарии у вас не тестируются, ясно. 

С чего вы взяли? Где написано, что "юнитами все и кончилось". Бред не пишите. У нас все уровни покрыты.

Отличный за интересный материал и изложение - очень легко и приятно читается. Тоже уже какое-то время думаю о вынесении максимального количества логики из компонентов и тоже смотрю в сторону MobX и DI (примерно как в Angular). Ваш подход выглядит очень интересно и думаю, стоит его попробовать, чтобы понять, чего в нем может не хватать. Спасибо.

DI как в Angular - не самая светлая идея. Лучше IoC как в $mol:

Я не имел в виду реализацию, я имел в виду в принципе его наличие. DI на фронте не очень популярен в других библиотеках и фреймворках. Я не поклонник Angular, но вариант с вынесением логики в сервисы и внедрение их в компоненты мне нравится и я думаю, как бы это объединить с прелестями React'а. Вы наверняка скажете, что эта проблема уже давно решена в $mol, причем более изящно, но так уж вышло, что я в нём не силен (хоть и читал почти все Ваши статьи про него) :)

как бы это объединить с прелестями React'а

В двух словах: DI-контейнер можно хранить в контексте. Наиболее продвинутые библиотеки, например, InversifyJS, умеют иерархию, что хорошо вписывается в идею вложенных провайдеров. Соответственно, крупный компонент может через useState создать свой экземпляр DI-контейнера с какими-то локальными классами, законнектить его к ближайшему родительскому, и обернуть свою начинку в провайдер с этим контейнером. Ну а хук, допустим, useServiceResolve, забирает контейнер из контекста.

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

Публикации

Истории