Автор говорит о проблемах тестирования, что оно делается сложным из-за того что нужно много зависимостей внедрить.
Хотя в своем примере тестирования тоже мокает зависимости, просто в новом подходе это делается чуть проще.
Ну а что мешает один раз написать моки для этих зависимостей и переиспользовать?
Новая архитектура нарушает SOLID, хотя автор этого не видит. Автор не понимает, что принцип единственной ответственности нужно рассматривать на определённом уровне абстракции. Например:
/router.tsx отвечает и за создание роутера, и за рендеринг
Нет, router.tsx отвечает за роутинг (единственная ответственность). А роутинг в себя включает и определение роутов, и их отображение отталкиваясь от window.location, и их композицию и тд.
И не стоит забывать, что хорошая архитектура нужна часто для удобства пользователей.
А роутинг в одном файле - очень удобно, нежели искать по проекту кто как и где рендерится
Автор, хотелось бы услышать продолжение, статья понравилась.
Да, и почему никто не говорит, что например Реакт уже определяет архитектуру? А еще никто не говорит, что DOM и есть архитектура, древовидная. Так что любые решение по архитектуре, уже ограничены архитектурой веба и фреймворка
Я не понял, почему когда абстрагируемся, связи остаются такими же, что и на низких уровнях (HTTP запрос между микросервисами).
Пример: На уровне класса, связи - прямые вызовы. Дальше по абстракции может быть например несколько процессов, которые вызывают друг друга через IPC. Дальше несколько сервисов через общую шину. Дальше несколько систем через gRPC.
И вот вызовы на разных уровнях могут отличаться не только количеством, но и сутью. Тут может показаться что я говорю про транспорт, но я имел ввиду, что когда мы абстрагируемся, то должны не только абстрагировать компоненты, но и связи между ними.
Окей проблема понятно. А вот почему через typescript решили, непонятно. Посмотрите в сторону zod (валидатор). Схема описывается через JS, но можно вывести typescript типы. + вам для low code платформы нужна будет валидация данных, например чтобы число было положительным, или чтобы строка была не длиннее чем X символов. А как вы это сделаете через typescript? Решение я описал в первом комментарии, и как будто больше подходит. А еще вопрос поддержки. Вот вы разобрались, понимаете как парсить ast дерево, но задача у вас простая, и придется поддерживать этот кому-то этот костыль....
А мне так и непонятно, какую проблему вы решили? Вы хотели использовать и typescript и proptypes, но описывать одну схему? Если да, то не легче было просто общую схему описать, которая бы генерила proptypes, и выводила typescript схему? То есть использовать code first, а не schema first
Да, простите, пропустил. Но мой пример лучше, так как пример в статье сломается, если дочерний компонент вызывет этот коллбэк в useLayoutEffect сразу при маунте.
Зачем он нужен? Это тоже самое что и useCallback, но без зависимостей. И его можно безопасно использовать в useEffect, а еще безопасно передавать как props
А автор не подумал, что если теперь есть soft delete, то старые запросы нужно тоже переписать? Ведь запрос на сущность, которая теперь soft удалена, должен не возвращать данные, а он продолжает возвращать. И если запросы бы были переписаны, то partial index бы сработал. Тут факап сразу в нескольких местах. А еще можно было синкануться с коллегами, чтобы хоть кто-то другой знал план, и бы заметил что что-то не так в этом плане...
Да, идея классная, абстрагироваться от всего, и сначала писать абстракцию, а потом реализацию. Только вот примеров мало, а потому что на практике этого очень сложно достичь. Да и на самом деле повышается сложность всего проекта, порог входа. Ну и не забываем, что за абстракцию придется заплатить. Как связать изменения данных в абстракции, и уведомление реализации о том, что ей нужно обновиться? В статье нет примера, но реакту, чтобы обновиться, нужно какое-то уведомление. Тогда у абстракции должен быть механизм подписки на свои уведомления. И вот мы получили MobX, который можно потом привязать позже к любому фреймворку, если грамотно все сначала настроить/разделить.
А еще возникают ситуации, когда данные должны обрабатываться, и храниться в таких структурах данных, которые удобно скармливать UI фреймворку, чтобы повысить производительность. Например я работаю с очень старыми устройствами, и мне нужно максимально эффективно использовать память, и удобно обновлять данные, а значит моя абстракция будет течь, и наоборот, принесет больше боли чем пользы.
А еще, как часто у вас возникал сценарий, когда вы хотели пересесть на другой фреймворк? Даже если у вас все абстрактно, пересесть на другой фреймворк будет очень дорого, ведь придется потратить много ресурсов на тестирование, и переписывание части реализации. И где-то, да и придется снова костылить утечки абстракции. Тогда не легче, просто разделить приложение, сделать из него микрофронты, и писать каждую часть на удобном себе фреймворке? Да, получим сложную доставку, сложную инициализацию, но зато каждую часть можно будет реализовать именно так, как нам удобно.
Да и в целом, в мире бэкенда давно двинулись от идей чистой/идеальной архитектуры, в сторону микросервисов. Не замечаете некую схожесть в решении этих проблем?
Я посмотрел на Fusor. В React тоже можно так сделать с помощью ref-ов. Все данные можно хранить в ref-ах, а когда нужен перерендер, использовать для этого useReducer:
Но это шаг назад, в отключение реактивности. Это может помочь только в тех случаях, когда нам ну очень важна производительность, и считаем уже каждый такт.
Fusor - не разделение ответственности, потому что философия React в том, чтобы реактивно показать данные, сматченные на верстку. И тут как раз и получается, одна ответственность - реактивно отображать данные, с чем React отлично справляется.
Согласен, но зависит от компонентов. Если в компоненте используется несколько хуков, useState/useEffect, то скорее мемоизация оправдана.
важный пример, когда чтобы избежать у дочернего элемента ререндер, можно родительский сделать враппером и покинуть в children
Вот вообще не важный, так как заставляет создавать структуру компонентов, оглядываясь на мемоизацию. Ну очень редко такое возможно применить. А вот кастомный хук useCallbackRefed, который в конце статьи приводится, я раньше нигде не видел, а он очень удобен для мемоизации
Для использования реакта в связке с MobX это отлично
Да, потому что MobX сам решает когда перерендерить. То есть, у нас теперь есть memo, который на каждый рендер будет сравнивать пропсы, и еще сама система реактивности MobX-а, которая не бесплатна.
А по аргументам
Нативный код
Ну приходится изучать MobX, и уже работать с ним:
Минимум лишней когнитивной нагрузки
Спорный момент, где-то может быть даже легче useState, а не писать целый класс
Минимум кода(отсутствие лишней лапшы)
Ну тут посложнее именно в MobX, так как получается легко написать классы, которые будут в 1000 строк в высоту. И потом с ними разбираться будет ой как больно. Это в целом зависит не от инструмента, а от навыков разработчков.
Всё оптимизировано из коробки
Перерендеров нет, но зато есть потребление памяти. Повторюсь - реактивность не бесплатная.
Не привязан к реакту,
Да, это хороший пункт. И тестировать такое легче.
Я не против MobX, даже наоборот, я за MobX. Но называть решения приведенные в статье полумерами, как-то некорректно. Часто нужно понимать как именно реакт работает, и я думаю, что это хорошая шпаргалка
То есть memo используется всегда! Насколько это хорошо? Наверное, если бы было очень хорошо, то разработчики React-а бы сделали компоненты мемоизированными по умолчанию. А есть у вас еще аргументы в пользу Mobx?
Если так мыслить, то мы можем просто ответить — перерендер, следствие изменения состояния. Да, это так и есть, но какую пользу дает такая интерпретация? А вот думать о причинах перерендерах так, как я представил в статье, очень полезно. Так сразу можно понять, какую стратегию оптимизации выбрать.
А вы не думали, что раз на хабре нету статьи про него, и вы первый, значит он и не нужен?)
Лучше не библиотеки учить, а идеи. Например, научить команду работать с ошибками и с исключениями отдельно. Понимать что есть ошибка (нормальный флоу, просто негативный), и исключение (исключительный флоу, когда все пошло не так)
Зачем так сложно? Откройте эти приложение в веб телеграме, посмотрите ссылку на iframe, скопируйте и поменяйте в ссылке "weba" на "android". Все, можно запускать в браузере и смотреть запросы. Смотрим запрос который отправляет кол-во кликов, копируем, и вставляем в Postman, смотрим как работает. Потом, если вы разработчик, то напишите на любом удобном себе языке простой скрипт, которые делает тотже самый http запрос
Автор говорит о проблемах тестирования, что оно делается сложным из-за того что нужно много зависимостей внедрить.
Хотя в своем примере тестирования тоже мокает зависимости, просто в новом подходе это делается чуть проще.
Ну а что мешает один раз написать моки для этих зависимостей и переиспользовать?
Новая архитектура нарушает SOLID, хотя автор этого не видит. Автор не понимает, что принцип единственной ответственности нужно рассматривать на определённом уровне абстракции. Например:
Нет, router.tsx отвечает за роутинг (единственная ответственность). А роутинг в себя включает и определение роутов, и их отображение отталкиваясь от window.location, и их композицию и тд.
И не стоит забывать, что хорошая архитектура нужна часто для удобства пользователей.
А роутинг в одном файле - очень удобно, нежели искать по проекту кто как и где рендерится
Автор, хотелось бы услышать продолжение, статья понравилась.
Да, и почему никто не говорит, что например Реакт уже определяет архитектуру? А еще никто не говорит, что DOM и есть архитектура, древовидная. Так что любые решение по архитектуре, уже ограничены архитектурой веба и фреймворка
А тот факт, что бот Яндекса будет иметь доступ к каналу, и сможет выкладывать что угодно, и когда угодно?
Я не понял, почему когда абстрагируемся, связи остаются такими же, что и на низких уровнях (HTTP запрос между микросервисами).
Пример: На уровне класса, связи - прямые вызовы. Дальше по абстракции может быть например несколько процессов, которые вызывают друг друга через IPC. Дальше несколько сервисов через общую шину. Дальше несколько систем через gRPC.
И вот вызовы на разных уровнях могут отличаться не только количеством, но и сутью. Тут может показаться что я говорю про транспорт, но я имел ввиду, что когда мы абстрагируемся, то должны не только абстрагировать компоненты, но и связи между ними.
Окей проблема понятно. А вот почему через typescript решили, непонятно.
Посмотрите в сторону zod (валидатор). Схема описывается через JS, но можно вывести typescript типы.
+ вам для low code платформы нужна будет валидация данных, например чтобы число было положительным, или чтобы строка была не длиннее чем X символов.
А как вы это сделаете через typescript?
Решение я описал в первом комментарии, и как будто больше подходит. А еще вопрос поддержки. Вот вы разобрались, понимаете как парсить ast дерево, но задача у вас простая, и придется поддерживать этот кому-то этот костыль....
А мне так и непонятно, какую проблему вы решили? Вы хотели использовать и typescript и proptypes, но описывать одну схему?
Если да, то не легче было просто общую схему описать, которая бы генерила proptypes, и выводила typescript схему?
То есть использовать code first, а не schema first
Да, простите, пропустил. Но мой пример лучше, так как пример в статье сломается, если дочерний компонент вызывет этот коллбэк в useLayoutEffect сразу при маунте.
А я бы еще добавил важный хук:
Зачем он нужен? Это тоже самое что и useCallback, но без зависимостей. И его можно безопасно использовать в
useEffect
, а еще безопасно передавать какprops
А автор не подумал, что если теперь есть soft delete, то старые запросы нужно тоже переписать? Ведь запрос на сущность, которая теперь soft удалена, должен не возвращать данные, а он продолжает возвращать. И если запросы бы были переписаны, то partial index бы сработал. Тут факап сразу в нескольких местах. А еще можно было синкануться с коллегами, чтобы хоть кто-то другой знал план, и бы заметил что что-то не так в этом плане...
Да, идея классная, абстрагироваться от всего, и сначала писать абстракцию, а потом реализацию. Только вот примеров мало, а потому что на практике этого очень сложно достичь. Да и на самом деле повышается сложность всего проекта, порог входа. Ну и не забываем, что за абстракцию придется заплатить. Как связать изменения данных в абстракции, и уведомление реализации о том, что ей нужно обновиться? В статье нет примера, но реакту, чтобы обновиться, нужно какое-то уведомление. Тогда у абстракции должен быть механизм подписки на свои уведомления. И вот мы получили MobX, который можно потом привязать позже к любому фреймворку, если грамотно все сначала настроить/разделить.
А еще возникают ситуации, когда данные должны обрабатываться, и храниться в таких структурах данных, которые удобно скармливать UI фреймворку, чтобы повысить производительность. Например я работаю с очень старыми устройствами, и мне нужно максимально эффективно использовать память, и удобно обновлять данные, а значит моя абстракция будет течь, и наоборот, принесет больше боли чем пользы.
А еще, как часто у вас возникал сценарий, когда вы хотели пересесть на другой фреймворк? Даже если у вас все абстрактно, пересесть на другой фреймворк будет очень дорого, ведь придется потратить много ресурсов на тестирование, и переписывание части реализации. И где-то, да и придется снова костылить утечки абстракции.
Тогда не легче, просто разделить приложение, сделать из него микрофронты, и писать каждую часть на удобном себе фреймворке? Да, получим сложную доставку, сложную инициализацию, но зато каждую часть можно будет реализовать именно так, как нам удобно.
Да и в целом, в мире бэкенда давно двинулись от идей чистой/идеальной архитектуры, в сторону микросервисов. Не замечаете некую схожесть в решении этих проблем?
А useRef? А держать состояние вне компонента?
Я посмотрел на Fusor. В React тоже можно так сделать с помощью ref-ов. Все данные можно хранить в ref-ах, а когда нужен перерендер, использовать для этого useReducer:
Но это шаг назад, в отключение реактивности. Это может помочь только в тех случаях, когда нам ну очень важна производительность, и считаем уже каждый такт.
Fusor - не разделение ответственности, потому что философия React в том, чтобы реактивно показать данные, сматченные на верстку. И тут как раз и получается, одна ответственность - реактивно отображать данные, с чем React отлично справляется.
Согласен, но зависит от компонентов. Если в компоненте используется несколько хуков, useState/useEffect, то скорее мемоизация оправдана.
Вот вообще не важный, так как заставляет создавать структуру компонентов, оглядываясь на мемоизацию. Ну очень редко такое возможно применить. А вот кастомный хук useCallbackRefed, который в конце статьи приводится, я раньше нигде не видел, а он очень удобен для мемоизации
Да, потому что MobX сам решает когда перерендерить. То есть, у нас теперь есть memo, который на каждый рендер будет сравнивать пропсы, и еще сама система реактивности MobX-а, которая не бесплатна.
А по аргументам
Ну приходится изучать MobX, и уже работать с ним:
Спорный момент, где-то может быть даже легче useState, а не писать целый класс
Ну тут посложнее именно в MobX, так как получается легко написать классы, которые будут в 1000 строк в высоту. И потом с ними разбираться будет ой как больно. Это в целом зависит не от инструмента, а от навыков разработчков.
Перерендеров нет, но зато есть потребление памяти. Повторюсь - реактивность не бесплатная.
Да, это хороший пункт. И тестировать такое легче.
Я не против MobX, даже наоборот, я за MobX. Но называть решения приведенные в статье полумерами, как-то некорректно. Часто нужно понимать как именно реакт работает, и я думаю, что это хорошая шпаргалка
Круто, мне нравится) Только вот есть один нюанс, если мы посмотрим исходники, то увидим:
Вот тут https://github.com/mobxjs/mobx/blob/main/packages/mobx-react/src/observer.tsx#L30 вызов передается observer-у, который в свою очередь просто мемоизирует компонент:
https://github.com/mobxjs/mobx/blob/main/packages/mobx-react-lite/src/observer.ts#L160
То есть memo используется всегда! Насколько это хорошо? Наверное, если бы было очень хорошо, то разработчики React-а бы сделали компоненты мемоизированными по умолчанию. А есть у вас еще аргументы в пользу Mobx?
Если так мыслить, то мы можем просто ответить — перерендер, следствие изменения состояния. Да, это так и есть, но какую пользу дает такая интерпретация? А вот думать о причинах перерендерах так, как я представил в статье, очень полезно. Так сразу можно понять, какую стратегию оптимизации выбрать.
А вы не думали, что раз на хабре нету статьи про него, и вы первый, значит он и не нужен?)
Лучше не библиотеки учить, а идеи. Например, научить команду работать с ошибками и с исключениями отдельно. Понимать что есть ошибка (нормальный флоу, просто негативный), и исключение (исключительный флоу, когда все пошло не так)
А насколько вообще корректно сравнивать транспорт с библиотекой? Они хоть и похожи в названиях, но делают абсолютно разные вещи
Зачем так сложно? Откройте эти приложение в веб телеграме, посмотрите ссылку на iframe, скопируйте и поменяйте в ссылке "weba" на "android". Все, можно запускать в браузере и смотреть запросы. Смотрим запрос который отправляет кол-во кликов, копируем, и вставляем в Postman, смотрим как работает. Потом, если вы разработчик, то напишите на любом удобном себе языке простой скрипт, которые делает тотже самый http запрос
В этом голосовании можно отменить голос, и снова сделать выбор. Вот если бы вы попробовали без этой фичи, было бы ближе к истине.![]()