В статье нет ни цифр, ни реальных итогов - только про то, как вы вышли на чуть лучшие показатели спустя какое-то время в конкретных условиях конкретной компании и состава ее команды, выбрав с нуля FSD, и сравнивая только со своим перфомансом. Сравнения с не-fsd не было - уже думаю десятый раз читаю статью, но кроме "документацию навряд ли так легко бы написали, если бы fsd не описал для нас" не вижу, при этом на свою документацию вы выбивали время завышенными сроками разработки.
"Традиционной" архитектурой обычно является ее отсутствие - нет. Наш FSD-проект безоговорочно лидирует по скорости погружения - нет. Мне тоже пора в палату по вашему мнению, или критично осмыслите то, что написали в комменте?)
Я плохо понимаю этот специфичный язык - "сущность", "обобщенные модели", "фичи", "свойства фичи", "бизнес-сущность", "юзер процесс". Я понимаю их значение на js - "сущность класса" в простом разговоре есть "инстанс класса", "модель" - TS-тип класса, "обобщенная модель" - в простом разговоре абстрактный класс. "фича" - процедурный функционал, "свойства фичи" возможно относится к алгоритму внутри функции или сайд-эффектам. "бизнес-сущность" обычно говорят, когда не знают, как описать что-то, но что-то комплексное, возможно включающее вызов апи и обработку ошибок. "юзер процесс" - форк системного процесса ноды.
Это мета-язык на основе js, который специфичен для структуры папок FSD - это я понимаю) но стоит ли настолько контекстуально делать аналоги понятий, и улучшает ли это понимаемость проекта?
В классике все просто и уникально:
Компонент - введенный много лет назад термин, означающий разметку + логику, вынесенную в отдельный класс или функцию. Это универсально в React, Angular, Vue, Solid, Web Components и других компонентных библиотеках. Аналога в js нет
Страница - семантичный комплексный элемент, существующий с зарождения веба. По любому URL из браузера открывается страница (конечно, если не из браузера - то другая история и другие протоколы). Она может содержать компоненты или быть цельной независимой html-compatible структурой. Но компоненты не могут содержать страницу в прямом смысле, только через iframe, то есть отдельный URL. Аналога в js нет
Сторы - это либо Data store, который содержит в себе только данные, либо Model/Service/UI/ViewModel Store, которые могут содержать методы. В классике выбирают наименование, которое им более привычно, но все равно эти 2 типа как правило четко разделяются и присутствуют. Аналога в js нет
Действия - это имеющие доступ сторам и апи функции. Они могут быть частями Model/Service/UI Store, а могут быть отдельными. В ряде случаев их могут называть "контроллерами", они могут читать, писать, вызывать - делать все, что нужно для функционала. Аналога в js нет (ну кроме слова "функция", но оно очень общее)
Слои - это семантическое группирование по функционалу и свойствам. API-слой отвечает за получение данных из внешних источников, Reaction-слой (это могут быть хуки, реакции СТМ, эвенты) - за реагирование на сигналы, подаваемые компонентами либо страницами. Слой сторов - объяснил выше. Слой констант, слой утилит + в зависимости от архитектуры проекта. Слои могут быть глобальными (для всего приложения или для отдельного приложения мультирепы), могут быть постраничными (в ряде интерпретаций - модульными) и могут быть локальными. В классике всегда соблюдается принцип "низшие знают о высших, но высшие не знают о низших". Нереально редкие исключения, о которых я даже не могу предположить, чтобы что-то глобальное знало о локальном, решаются через события: LocalUiStore -> call event ('someName', payload) -> GlobalReactionStore -> get event ('someName', payload) -> setData to GlobalStore. Но в классике такого не бывает, чисто теория
Если в контексте вашего ответа, то "бизнес-сущность продукт" - это компонент src/components/Product. "следующие сущности: "карточка маленькая", "карточка большая", "список продуктов" решаются либо пропом к компоненту, либо маппером, когда в src/components/Product лежат ProductSmall.tsx, ProductBig.tsx, ProductList.tsx, хотя никто бы не стал список продуктов помещать в один компонент.
Авторизация в классике - это не "фича" в контекстуальном понимании FSD, это действие. Оно вызывает апи с параметрами и либо выдает нотификацию, либо кладет в стор юзера. Этот экшен могут вызывать как компоненты, так и страницы. В ряде архитектур могут и Model/Service/UI Store. "оплата", или "добавление в корзину", или "удаление из корзины" - то же самое. И в действии могут быть "апи и, например, оптимистичная мутация данных на фронте".
В целом классика максимально всеобъемлюща, уникальна для структуры папок, не пересекается с js по неймингу. Но она требует, как и FSD, определенного опыта для организации. Но документации для нее по факту нет, поэтому FSD в этом плане выигрывает. Но в большинстве случаев документация и не нужна, так как фронтендеры и так мыслят этими сущностями, и классика есть отражение логичного мышления.
Скажу сразу, с FSD в проде я работал только единожды (в остальном только с классической структурой), все что здесь напишу - мысленные эксперименты + небольшой опыт.
"Изолированность и модульность", включающая в себя удобства в виде "независимая от других разработка модуля одним разработчиком", "изоляция кода и багов", "масштабируемость".
Это звучит безусловно непротиворечиво - каждый пишет код в своей папке, возможно со своим стилем кода, несет за него ответственность и это вдохновляет писать качественно. Однако налицо и катастрофический недостаток в виде дубляжа. 5 разработчиков на своих 5 страницах делают одно и то же обращение к апи, модели запроса-ответа, реализацию обработки ошибок со своими текстами (условно - понадобилось стянуть справочник Cities с бэка). Также они пишут свои кнопки, свои формы, свою локализацию, свои интерфейсы к компонентам.
В ряде случаев все это будет уникальным для страницы / виджета / фичи, а в ряде случаев (предположу что 30%) это будет дубляжом. Соответственно если апи бэка изменился для этой ручки - то нужно менять в 5 модулях, если кнопку нужно перестилить или добавить состояние загрузки - тоже в 5 модулях нужно искать, разбираться в каждой реализации и делать уникальные решения для каждой уникальной, но похожей, кнопки.
В итоге на этапе разработки ресурс времени разработчиков экономится, однако в перспективе поддержки из-за неединообразия в реализации, но единообразия в функционале, выходит кратное возрастание затрат времени.
Класть компоненты в shared/ui для переиспользуемости - безусловно частично выправляет ситуацию. Однако с этим теряется и "модульность" - при разработке опять же 5 разрабам нужно добавить разный функционал в кнопку (иногда stopPropagation, иногда внезапно появившийся новый вариант цвета, иногда дополнительную анимацию). Они начинают "толкаться" в одном компоненте и в определенных проектах (например с релизным циклом в 1 день) решают ее скопировать в свой модуль, дополнительно увеличивая дубляж, так как решать все потенциальные мердж-конфликты заранее с другими разрабами и их локальными ветками через чат - это пытка.
Потом, разумеется, если разработка активная, факт дубляжа забывается, а через год разраб, делающий редизайн, видит десятки похожих кнопок с разной реализацией. Это история из жизни - как раз делал редизайн в таком проекте, который из-за начального решения следовать принципу изоляции начал реализовывать это через дубляж.
У вас идеально скомплектованная команда и хорошие процессы - перед стартом фронт-задачи есть ТЗ, дизайн, готовый бэк и время на анализ каждой задачи с командой + достаточное время на реализацию + есть время писать техническую документацию и обосновывать принятые решения + время на декомпозицию и слежение за статусами подзадач. К сожалению, таких компаний не так много, и часто есть только грубое ТЗ и примерный дизайн, и за 3 дня нужно сделать одновременно и параллельно дизайн + фронт + бэк и корректно интегрироваться, да и в процессе работы над задачей требования меняются вплоть до последнего часа. При этом бизнес очень жестко настаивает, что "там дел на 5 минут".
Это часто встречается в стартапах, которым за пару месяцев нужно сделал свой Гугл (а им всем нужно) и сделать это лучше, чем у конкурентов. Здесь на мой взгляд выиграет FSD за счет изоляции, если разрабов 3+, и проиграет классике, если меньше - классика заточена под переиспользуемость и глобальность + четкую структуру слоев, и из-за динамичных изменений и быстрой разработки проще поправить в 1 месте для всех 10 страниц, чем в каждом из них, так как нет достаточных ресурсов разработчиков.
Также вы говорили, что уделите основное внимание "процессу разработки на FSD", но большая часть статьи - про Agile (управление задачами, ретроспектива, распределение задач, обсуждение с коллегами, документирование, разбиение на итерации) - это универсальные принципы, никак не зависящие от структуры проекта, и те же самые этапы по Agile должны быть везде. В том числе в классике было бы то же самое (только с меньшей детализацией) при наличии таких же ресурсов и команды, только вместо обсуждений "куда положить то или иное и как назвать" больше бы обсуждались детали реализации (дебаунсы, стоит ли вынести функционал на слой выше для переиспользуемости, обсуждение с бэком удобного контракта апи).
В итогах вы тоже сравниваете сами с собой - насколько команда привыкла к структуре, насколько вначале было неэффективное разбиение задач, как вы постепенно улучшали структуру проекта и за счет "притирки" (давали бизнесу прежние сроки, а делали за меньшее время) высвободили время для рефакторинга и документации. Которая, к слову, в классике пишется очень просто, хотя ее по факту никто не читает - классика по большей части самодокументируется семантикой и слоями без искусственного разделения.
То есть статья получилась не про FSD, а про то, как хорошо когда в компании много ресурсов и времени, и можно пробовать что угодно, и все будет хорошо)
P.S. я делал похожий проект, только в соло и намного сложней (так как нужно было еще 3 проекта параллельно делать - приложения для сборщиков и курьеров, для мониторинга сотрудников, клиентский сайт) - на классическую структуру это все прекрасно ложится за счет ее структурированности и переиспользуемости, и все, что увидел в статье, никак не сподвигло бы на переход. Если нужно, могу рассказать о структуре этих страниц, но это вряд ли нужно - в классике и так все разбираются, поэтому и статей по ней нет.
Конечно, Button может получать данные наиболее удобным способом - через глобальный контекст, через прямой импорт глобального стора, через пропсы. Когда может быть удобнее использовать глобальный стор, а не пропсы?
Например, кнопка читает глобальную тему стилей и нужно на лету менять цвет всех кнопок на проекте. Допустим, что css variables не используются, а тема хранится в глобальном сторе или передается как во многих UI библиотеках через верхнеуровневый контекст, либо css in js. В этом случае безусловно через пропсы каждой кнопке передавать текущий цвет нелогично.
Второй пример - локализация. Есть 10 языков, данные текстов хранятся в глобальном сторе. Button сделали с таким интерфейсом, что текст опционален - `<Button text={store.localize('Ok')} />`, а по дефолту кнопка имеет текст Ok. Это часто используется например в модалках или конфермах, когда есть 2 кнопки - отказ и принять, и не хочется каждый раз тексты передавать через пропы. Разумеется, кнопка возьмет дефолтный текст из глобального стора, если хранение локализации находится там.
Третий пример - есть специфичная кнопка src/components/ButtonGetUser, которая используется на 5 страницах и должна вызвать апи получения данных по пользователю, и вызвать модальное окно с показом его данных. Этот кейс ближе к админкам. Зачем дублировать логику, передавая в onClick одну и ту же логику, если этот компонент может сам обратиться к глобальному апи и глобально вызвать модалку - DRY принцип в действии.
Я не задумываюсь о разделении "глупый компонент", "тупой компонент", "компонент, принимающий все только через пропы", "компонент, способный сам вызывать глобальные действия" - если того требует логика, компонент может трансформироваться между этими состояниями без лишней когнитивной нагрузки и выделения дополнительных сущностей - адаптеров, коннекторов, контроллеров, HOC, виджетов и что только не видел еще для "соблюдения неких принципов".
Про декорирование не понял, я вроде о нем ничего не говорил)
В классической есть домены - это папка pages. Логичная и непротиворечивая система, когда страница или группа страниц есть отдельная сущность, которую можно вынести в микрофронт при необходимости. Не нужно других синтетических абстракций фич, энтити, виджетов - "страница" есть семантическая сущность веб-приложений изначально с изобретения веба. Она включает в себя отображение и бизнес-логику, изолированную от остальных страниц.
С развитием компонентного подхода пришел концепт переиспользуемости, то есть в src/components кладутся однообразные компоненты типа Button, Form, Cart. Они могут использовать глобальные вызовы api и читать данные из глобальных сторов globalStores, но могут и через пропы принимать что-то кастомное, специфичное для страницы, на которой используются. Это логичное, минималистичное и удобное деление и называется "классической структурой", и в статье показан ее кастомный вариант, который хорошо масштабируется и интуитивно используется, несмотря на определенную чрезмерную интерпретацию (типа pages и layouts почему-то кладутся в components).
Внимательно читаю вашу дискуссию, но я тут на светлой стороне, хотя в словах Клерика она звучит грубо иногда)
В этом треде вы что-то в абстракции ушли) На деле оба за домены, просто в классической структуре домен это `pages/somePage`, а в фсд это сгруппированно в некую features или виджет или куда там еще. Конечно, в классической все удобнее - можно решить на уровне роутера. Допустим, некая страница разрослась до больших размеров и у нее десяток связанных подстраниц, решили выделить в отдельный проект - в этом случае выносится только папка страницы
Вот и получился микрофронт. Внутрь переданы пропсы с глобальными слоями, которые микрофронтом кладутся в контекст и используются оттуда вместо es-импортов (ts-типы линкуются разными способами). При этом он внутри по той же самой "классической" структуре имеет те же самые папки, просто все его слои работают только внутри этого микрофронта.
Тут не согласен с "крупный проект через монорепо - уже плохая идея". Скорее делать через микрофронты - плохая идея. В архитектуре, где каждая страница - независима и загружается асинхронным чанком, до 50 страниц не будет проблем. Потом может понадобиться вынести "страница и связанные с ней 10 страниц" в отдельный пакет, разрабатываемый отдельной командой, но это хорошо ложится в монорепо, и плохо ложится в микрофронт.
Я не про fsd сейчас, просто зацепился за сам концепт)
"получаем просто монстра, с которым не понятно, как работать" - возможно это и цель. На Хабре очень много статей про то, что "не сделаешь баг - долго не проработаешь", "не сделаешь сложный код - станешь заменим". Это даже пыталось стать трендом, и ряд проектов специально берет нишевые подходы, чтобы долго думать что такое виджеты, энтити и фичи, хотя ни в бизнес-требованиях, ни в логике приложения этого нет.
Я безусловно за классическую структуру проекта, так же как и за семантическое отношение к написанию кода - они должны быть читаемыми и логичными, как предложения, и так же логично располагаться в проекте. Но по сложно понимаемым причинам ряд разработчиков идет сложным путем, возможно как раз из-за "авторитетности" мнений и выбирает даже Redux или что похуже.
Мутабельность можно сделать и с помощью других паттернов, прокси вовсе не обязательны. Главное - интерфейс, что при мутации значения происходит ререндер. Что не нужно делать ни useReducer, ни setValue. И жесткие ограничения на интеграцию с другими системами реактивности Реакт тоже не красят - намерение плотно привязать на его экосистему и его подходы как раз и мешают более эффективным решениям "стать общепринятым стандартом". Как бы сообщество ни хотело исправить это - получаем только новую пачку хуков, решающих проблемы, которых и не должно было бы быть.
Статья просто пронизана болью, даже выводы дочитать не смог. Болью веб-студии, которой попадаются классические заказчики, которые не знают, что им нужно, и не понимают сложности внедрения и изменения функционала. Будто вернулся на 10 лет назад, когда тоже в такой конторе работал) К сожалению, это очень неблагодарная и рисковая сфера как для бизнеса, так и для разработчиков. И научить всех клиентов "сформировать план работ и выстроить грамотную коммуникацию" точно не получится. Остается посочувствовать и пожелать удачи.
Это повод сказать, что Реакт очень медленно развивается, и этот релиз - в основном работа над ошибками, а не большой шаг вперед. Есть библиотеки как mobx, которые уже давно продвинули его на несколько шагов вперед, сделав удобный механизм работы со стейтом, и оставив Реакт только для рендера компонентов.
Я думаю, что именно на этом Реакт и должен концентрироваться, а не перерастать в большую неэффективную систему, пробуя на аудитории новые подходы в виде хуков (функции в функции с сайд-эффектами и невидимым кешем) со строгими правилами (или не очень строгими, а очень странными, как в случае с новым use) или серверные компоненты.
К сожалению, state.props = props; очень не понравится mobx, если в MyComponentLocalState будет зависимость от этих пропов или геттеры - скажет, что синхронно менять значения при рендере нельзя. Поэтому придется использовать useEffect
Спасибо за статью. Под "в этот раз опирались на более объективные критерии, и выбор был очевидным", как понимаю, имеется в виду MobX? Если так - могу посоветовать пару удобных подходов mobx-stateful-fn и mobx-use-store - они дают очень удобные механизмы. Там же рядом и неплохой роутер, вдруг пригодится)
Согласен, главный минус - малое коммьюнити и количество библиотек. В реальных проектах нужен очень большой выбор библиотек, потому что все с нуля писать - не только занимает много времени (даже для селектов библиотеки пишутся годами из-за совместимости с большим количеством браузеров), но и требует намного более тщательной поддержки, что усложняет проекты. То есть упрощение написания функционала в SolidJS по сравнению с React становится незначительным по сравнению с тем, что нужно самостоятельно реализовывать на порядки больше компонентов.
Почему плохо - я привел несколько аргументов в комментарии выше. Про сильную связанность с view, про сложность с SSR, про невозможность вызова вне Реакта (например до рендеринга приложения или в ответ на какую-то реакцию), про сложности генерации реалтаймовых валидаторов, про отсутствие возможности протестировать вне компонентов, про слабую связь со стором (например для вывода нотификаций). Как хорошо - делать независимый от фреймворка слой апи, при необходимости передавая в него разные сущности типа сторов, экшенов, локализации. Пример я приводил здесь https://github.com/mobxjs-ru/api-by-config
На мой взгляд, сокращение имен классов - это экономия на спичках в ущерб DX.
При сжатии gzip/brotli экономия на большой html будет в байтах, максимум - в паре килобайт. Это не повлияет на оценку Lighthouse и на пользовательский опыт, если вы не гонитесь за тысячными долями процента перфоманса
При чтении дерева элементов в инспекторе видим несемантичные j1 g83 классы, соответственно как найти место в коде, в котором нужно поправить стиль? Можно переключиться на React Dev Tools, выбрать элемент, потом обратно в инспектор, проверить там новые стили, и дальше - копать код проекта, перебирая папки и файлы в рандомном порядке. Проблема усугубляется тем больше, чем больше разработчиков в компании, и чем хуже знаешь кодовую базу проекта. Частично проблема решится CSS maps, но тоже много лишних телодвижений.
При этом семантичные CSS Modules сразу покажут где находится файл - src-components-layouts-userMenu src-pages-user.
При регистрации ошибки в Sentry например, разработчик смотрит путь, который пользователь накликал (в Сентри это логируется). И в случае сокращенных имен он увидит click[.j1] - как с этим работать? Скорость дебага значительно увеличится.
Для автотестов стабильные имена [path][name]__[local]--[hash:base64:5] можно использовать как уникальные идентификаторы элементов в ряде случаев и обойтись без data-test-id или id (если не учитывать в селекторе hash). Также можно дать не сильно разбирающемуся в автотестах сотруднику визуальный инструмент накликивания пользовательских сценариев. Тогда он передаст разработчику семантичный список действий click[.src-components-layouts-userMenu] и разработчик при необходимости очень быстро добавит id либо оставит селекторы в оригинальном виде, если эти классы достаточно специфичны.
Итого - сокращением названий классов мы улучшили пользовательский опыт на неизмеримо малую долю, при этом кардинально усложнили работу верстальщикам, разработчикам, тестировщикам и в целом тратим больше денег компании каждый день.
Должен быть контект для решения задачи. "Наш продукт имеет систему виджетов, которые могут получать данные из разных источников, поэтому рассмотрим вариант когда url для фетча передается в компонент из внешнего источника". Дальше - "допустим, вы решили делать фетчинг через кастомный хук, расскажите, какие минусы и плюсы у этого решения и как бы вы это реализовали". Вот это была бы достойная задача на собеседование, проверяющая глубину опыта. А без контекста - это конь в вакууме, проверяющий читали ли вы документацию Реакта и fetch, чтобы проверить самую базовую базу
Ну вот позвольте рассказать, как я бы воспринял эту задачу, чтобы объяснить мою позицию. Я работал в десятке компаний и сделал больше сотни сайтов, половина - на Реакте. Каждый день вырабатывал практики, как организовать слои приложения так, чтобы они были одновременно независимыми и при этом легко интегрировались друг в друга. Я ожидаю, что на собеседовании проверят не то, как я различаю == и ===, и не чему будет равно [] == true, а проверят реальный опыт на реальных задачах, чтобы я смог показать глубину опыта. Мне дают на собеседовании задачу про "компонент получает url", на что я сразу напрягаюсь, "компонент функциональный", на что соответственно сразу думаю про SSR, "хук, которых фетчит данные", на что сразу думаю - куда я попал? Первое, что я отвечу - так делать нельзя и только джуны могут так делать. Расскажу про все, что перечислил в первом посте, займет минут 20. У собеседующего время ограничено, он не проверил мое "знание хуков Реакта", но получил море как бы не связанной с изначальным намерением информации.
Адекватный собеседующий скажет "тогда давайте заменим получение данных по апи просто на асинхронную функцию", и тогда я сразу соглашуть написать то решение, которое приведено в статье - с зависимостями во втором аргументе useEffect и с возвращением псевдо-прерывающей функции, чтобы при размаунте результат не обрабатывался. Конечно, добавил бы про обсерваблы, про варианты обхода ручного (!cancelled), про унификацию обработки, про типизацию ошибок, про коннект к разным частям приложения, про слои и т.п. еще на час беседы, но т.к. время ограничено и я четко понял, что задачей хотят проверить "понимание основ React", то опустил бы.
При этом внутренне я, конечно, ругнулся бы - потому что спросили бы что-то более приближенное к реальности, а не пример из доки Реакта, который все 100 раз читали.
В итоге - мы оба потратили много времени на обсуждение организации апи и по итогу часа я выполнил 1 задачу из 3. Собеседующий так и записал, пришло письмо от HR "вы нам не подходите". История не особо выдуманная, на опыте было немало такого) Может, это мои заморочки, что не готов обсуждать совсем базовые штуки (шутка про Яндекс, в который я прошел около 20 собесов в разное время, чтобы увидеть какая дичь у них в коде), но и на джуна я не собеседуюсь - а такие вопросы тем не менее в каждой первой компании.
Поэтому для экономии времени всех и для получения специалистов повыше джуна задача из статьи не подходит, даже если интервьюер супер-адекватный и готов зачесть разговор на час за решение всех задач.
Все так, для статьи "любимая задача про знание Реакт" придраться больше и не к чему, кроме как к тому, что интервьюеры "могут не обладать соответствующими знаниями". Но вот к "Вот такой вариант я считаю "идеальным"" придраться можно очень много к чему) потому что код в статье - это решение джуна, неприменимое к реальности
Да, как отправную точку - отлично, но в выводе статьи - "Задачка позволяет проверить, как кандидат понимает устройство рендеринга React...", то есть ответ "так нельзя" скорее всего приведет к тому, что интервьюер либо решит, что задачу кандидат не решил и не знает основ, либо не сможет проверить эти знания потому что под рукой нет более подходящей задачи. То есть все же считаю что правильно сказал - определенные разрабы отсекутся этой задачей, потому что она проверяет для интервьюера одно, а для соискателя может быть совсем другое (знание архитектур, а не setState и useEffect Реакта)
Вы же написали статью, чтобы другие люди пользовались в своей практике? И такие найдутся, которые будут считать что это задача на хуки, а кандидат думать что на апи - и разойдутся.
В статье нет ни цифр, ни реальных итогов - только про то, как вы вышли на чуть лучшие показатели спустя какое-то время в конкретных условиях конкретной компании и состава ее команды, выбрав с нуля FSD, и сравнивая только со своим перфомансом. Сравнения с не-fsd не было - уже думаю десятый раз читаю статью, но кроме "документацию навряд ли так легко бы написали, если бы fsd не описал для нас" не вижу, при этом на свою документацию вы выбивали время завышенными сроками разработки.
"Традиционной" архитектурой обычно является ее отсутствие - нет. Наш FSD-проект безоговорочно лидирует по скорости погружения - нет. Мне тоже пора в палату по вашему мнению, или критично осмыслите то, что написали в комменте?)
Я плохо понимаю этот специфичный язык - "сущность", "обобщенные модели", "фичи", "свойства фичи", "бизнес-сущность", "юзер процесс". Я понимаю их значение на js - "сущность класса" в простом разговоре есть "инстанс класса", "модель" - TS-тип класса, "обобщенная модель" - в простом разговоре абстрактный класс. "фича" - процедурный функционал, "свойства фичи" возможно относится к алгоритму внутри функции или сайд-эффектам. "бизнес-сущность" обычно говорят, когда не знают, как описать что-то, но что-то комплексное, возможно включающее вызов апи и обработку ошибок. "юзер процесс" - форк системного процесса ноды.
Это мета-язык на основе js, который специфичен для структуры папок FSD - это я понимаю) но стоит ли настолько контекстуально делать аналоги понятий, и улучшает ли это понимаемость проекта?
В классике все просто и уникально:
Компонент - введенный много лет назад термин, означающий разметку + логику, вынесенную в отдельный класс или функцию. Это универсально в React, Angular, Vue, Solid, Web Components и других компонентных библиотеках. Аналога в js нет
Страница - семантичный комплексный элемент, существующий с зарождения веба. По любому URL из браузера открывается страница (конечно, если не из браузера - то другая история и другие протоколы). Она может содержать компоненты или быть цельной независимой html-compatible структурой. Но компоненты не могут содержать страницу в прямом смысле, только через iframe, то есть отдельный URL. Аналога в js нет
Сторы - это либо Data store, который содержит в себе только данные, либо Model/Service/UI/ViewModel Store, которые могут содержать методы. В классике выбирают наименование, которое им более привычно, но все равно эти 2 типа как правило четко разделяются и присутствуют. Аналога в js нет
Действия - это имеющие доступ сторам и апи функции. Они могут быть частями Model/Service/UI Store, а могут быть отдельными. В ряде случаев их могут называть "контроллерами", они могут читать, писать, вызывать - делать все, что нужно для функционала. Аналога в js нет (ну кроме слова "функция", но оно очень общее)
Слои - это семантическое группирование по функционалу и свойствам. API-слой отвечает за получение данных из внешних источников, Reaction-слой (это могут быть хуки, реакции СТМ, эвенты) - за реагирование на сигналы, подаваемые компонентами либо страницами. Слой сторов - объяснил выше. Слой констант, слой утилит + в зависимости от архитектуры проекта. Слои могут быть глобальными (для всего приложения или для отдельного приложения мультирепы), могут быть постраничными (в ряде интерпретаций - модульными) и могут быть локальными. В классике всегда соблюдается принцип "низшие знают о высших, но высшие не знают о низших". Нереально редкие исключения, о которых я даже не могу предположить, чтобы что-то глобальное знало о локальном, решаются через события: LocalUiStore -> call event ('someName', payload) -> GlobalReactionStore -> get event ('someName', payload) -> setData to GlobalStore. Но в классике такого не бывает, чисто теория
Если в контексте вашего ответа, то "бизнес-сущность продукт" - это компонент src/components/Product. "следующие сущности: "карточка маленькая", "карточка большая", "список продуктов" решаются либо пропом к компоненту, либо маппером, когда в src/components/Product лежат ProductSmall.tsx, ProductBig.tsx, ProductList.tsx, хотя никто бы не стал список продуктов помещать в один компонент.
Авторизация в классике - это не "фича" в контекстуальном понимании FSD, это действие. Оно вызывает апи с параметрами и либо выдает нотификацию, либо кладет в стор юзера. Этот экшен могут вызывать как компоненты, так и страницы. В ряде архитектур могут и Model/Service/UI Store. "оплата", или "добавление в корзину", или "удаление из корзины" - то же самое. И в действии могут быть "апи и, например, оптимистичная мутация данных на фронте".
В целом классика максимально всеобъемлюща, уникальна для структуры папок, не пересекается с js по неймингу. Но она требует, как и FSD, определенного опыта для организации. Но документации для нее по факту нет, поэтому FSD в этом плане выигрывает. Но в большинстве случаев документация и не нужна, так как фронтендеры и так мыслят этими сущностями, и классика есть отражение логичного мышления.
Скажу сразу, с FSD в проде я работал только единожды (в остальном только с классической структурой), все что здесь напишу - мысленные эксперименты + небольшой опыт.
"Изолированность и модульность", включающая в себя удобства в виде "независимая от других разработка модуля одним разработчиком", "изоляция кода и багов", "масштабируемость".
Это звучит безусловно непротиворечиво - каждый пишет код в своей папке, возможно со своим стилем кода, несет за него ответственность и это вдохновляет писать качественно. Однако налицо и катастрофический недостаток в виде дубляжа. 5 разработчиков на своих 5 страницах делают одно и то же обращение к апи, модели запроса-ответа, реализацию обработки ошибок со своими текстами (условно - понадобилось стянуть справочник Cities с бэка). Также они пишут свои кнопки, свои формы, свою локализацию, свои интерфейсы к компонентам.
В ряде случаев все это будет уникальным для страницы / виджета / фичи, а в ряде случаев (предположу что 30%) это будет дубляжом. Соответственно если апи бэка изменился для этой ручки - то нужно менять в 5 модулях, если кнопку нужно перестилить или добавить состояние загрузки - тоже в 5 модулях нужно искать, разбираться в каждой реализации и делать уникальные решения для каждой уникальной, но похожей, кнопки.
В итоге на этапе разработки ресурс времени разработчиков экономится, однако в перспективе поддержки из-за неединообразия в реализации, но единообразия в функционале, выходит кратное возрастание затрат времени.
Класть компоненты в shared/ui для переиспользуемости - безусловно частично выправляет ситуацию. Однако с этим теряется и "модульность" - при разработке опять же 5 разрабам нужно добавить разный функционал в кнопку (иногда stopPropagation, иногда внезапно появившийся новый вариант цвета, иногда дополнительную анимацию). Они начинают "толкаться" в одном компоненте и в определенных проектах (например с релизным циклом в 1 день) решают ее скопировать в свой модуль, дополнительно увеличивая дубляж, так как решать все потенциальные мердж-конфликты заранее с другими разрабами и их локальными ветками через чат - это пытка.
Потом, разумеется, если разработка активная, факт дубляжа забывается, а через год разраб, делающий редизайн, видит десятки похожих кнопок с разной реализацией. Это история из жизни - как раз делал редизайн в таком проекте, который из-за начального решения следовать принципу изоляции начал реализовывать это через дубляж.
У вас идеально скомплектованная команда и хорошие процессы - перед стартом фронт-задачи есть ТЗ, дизайн, готовый бэк и время на анализ каждой задачи с командой + достаточное время на реализацию + есть время писать техническую документацию и обосновывать принятые решения + время на декомпозицию и слежение за статусами подзадач. К сожалению, таких компаний не так много, и часто есть только грубое ТЗ и примерный дизайн, и за 3 дня нужно сделать одновременно и параллельно дизайн + фронт + бэк и корректно интегрироваться, да и в процессе работы над задачей требования меняются вплоть до последнего часа. При этом бизнес очень жестко настаивает, что "там дел на 5 минут".
Это часто встречается в стартапах, которым за пару месяцев нужно сделал свой Гугл (а им всем нужно) и сделать это лучше, чем у конкурентов. Здесь на мой взгляд выиграет FSD за счет изоляции, если разрабов 3+, и проиграет классике, если меньше - классика заточена под переиспользуемость и глобальность + четкую структуру слоев, и из-за динамичных изменений и быстрой разработки проще поправить в 1 месте для всех 10 страниц, чем в каждом из них, так как нет достаточных ресурсов разработчиков.
Также вы говорили, что уделите основное внимание "процессу разработки на FSD", но большая часть статьи - про Agile (управление задачами, ретроспектива, распределение задач, обсуждение с коллегами, документирование, разбиение на итерации) - это универсальные принципы, никак не зависящие от структуры проекта, и те же самые этапы по Agile должны быть везде. В том числе в классике было бы то же самое (только с меньшей детализацией) при наличии таких же ресурсов и команды, только вместо обсуждений "куда положить то или иное и как назвать" больше бы обсуждались детали реализации (дебаунсы, стоит ли вынести функционал на слой выше для переиспользуемости, обсуждение с бэком удобного контракта апи).
В итогах вы тоже сравниваете сами с собой - насколько команда привыкла к структуре, насколько вначале было неэффективное разбиение задач, как вы постепенно улучшали структуру проекта и за счет "притирки" (давали бизнесу прежние сроки, а делали за меньшее время) высвободили время для рефакторинга и документации. Которая, к слову, в классике пишется очень просто, хотя ее по факту никто не читает - классика по большей части самодокументируется семантикой и слоями без искусственного разделения.
То есть статья получилась не про FSD, а про то, как хорошо когда в компании много ресурсов и времени, и можно пробовать что угодно, и все будет хорошо)
P.S. я делал похожий проект, только в соло и намного сложней (так как нужно было еще 3 проекта параллельно делать - приложения для сборщиков и курьеров, для мониторинга сотрудников, клиентский сайт) - на классическую структуру это все прекрасно ложится за счет ее структурированности и переиспользуемости, и все, что увидел в статье, никак не сподвигло бы на переход. Если нужно, могу рассказать о структуре этих страниц, но это вряд ли нужно - в классике и так все разбираются, поэтому и статей по ней нет.
Конечно, Button может получать данные наиболее удобным способом - через глобальный контекст, через прямой импорт глобального стора, через пропсы. Когда может быть удобнее использовать глобальный стор, а не пропсы?
Например, кнопка читает глобальную тему стилей и нужно на лету менять цвет всех кнопок на проекте. Допустим, что css variables не используются, а тема хранится в глобальном сторе или передается как во многих UI библиотеках через верхнеуровневый контекст, либо css in js. В этом случае безусловно через пропсы каждой кнопке передавать текущий цвет нелогично.
Второй пример - локализация. Есть 10 языков, данные текстов хранятся в глобальном сторе. Button сделали с таким интерфейсом, что текст опционален - `<Button text={store.localize('Ok')} />`, а по дефолту кнопка имеет текст Ok. Это часто используется например в модалках или конфермах, когда есть 2 кнопки - отказ и принять, и не хочется каждый раз тексты передавать через пропы. Разумеется, кнопка возьмет дефолтный текст из глобального стора, если хранение локализации находится там.
Третий пример - есть специфичная кнопка src/components/ButtonGetUser, которая используется на 5 страницах и должна вызвать апи получения данных по пользователю, и вызвать модальное окно с показом его данных. Этот кейс ближе к админкам. Зачем дублировать логику, передавая в onClick одну и ту же логику, если этот компонент может сам обратиться к глобальному апи и глобально вызвать модалку - DRY принцип в действии.
Я не задумываюсь о разделении "глупый компонент", "тупой компонент", "компонент, принимающий все только через пропы", "компонент, способный сам вызывать глобальные действия" - если того требует логика, компонент может трансформироваться между этими состояниями без лишней когнитивной нагрузки и выделения дополнительных сущностей - адаптеров, коннекторов, контроллеров, HOC, виджетов и что только не видел еще для "соблюдения неких принципов".
Про декорирование не понял, я вроде о нем ничего не говорил)
В классической есть домены - это папка pages. Логичная и непротиворечивая система, когда страница или группа страниц есть отдельная сущность, которую можно вынести в микрофронт при необходимости. Не нужно других синтетических абстракций фич, энтити, виджетов - "страница" есть семантическая сущность веб-приложений изначально с изобретения веба. Она включает в себя отображение и бизнес-логику, изолированную от остальных страниц.
С развитием компонентного подхода пришел концепт переиспользуемости, то есть в src/components кладутся однообразные компоненты типа Button, Form, Cart. Они могут использовать глобальные вызовы api и читать данные из глобальных сторов globalStores, но могут и через пропы принимать что-то кастомное, специфичное для страницы, на которой используются. Это логичное, минималистичное и удобное деление и называется "классической структурой", и в статье показан ее кастомный вариант, который хорошо масштабируется и интуитивно используется, несмотря на определенную чрезмерную интерпретацию (типа pages и layouts почему-то кладутся в components).
Внимательно читаю вашу дискуссию, но я тут на светлой стороне, хотя в словах Клерика она звучит грубо иногда)
В этом треде вы что-то в абстракции ушли) На деле оба за домены, просто в классической структуре домен это `pages/somePage`, а в фсд это сгруппированно в некую features или виджет или куда там еще. Конечно, в классической все удобнее - можно решить на уровне роутера. Допустим, некая страница разрослась до больших размеров и у нее десяток связанных подстраниц, решили выделить в отдельный проект - в этом случае выносится только папка страницы
Вот и получился микрофронт. Внутрь переданы пропсы с глобальными слоями, которые микрофронтом кладутся в контекст и используются оттуда вместо es-импортов (ts-типы линкуются разными способами). При этом он внутри по той же самой "классической" структуре имеет те же самые папки, просто все его слои работают только внутри этого микрофронта.
Это прекрасно ложится на большие проекты.
Тут не согласен с "крупный проект через монорепо - уже плохая идея". Скорее делать через микрофронты - плохая идея. В архитектуре, где каждая страница - независима и загружается асинхронным чанком, до 50 страниц не будет проблем. Потом может понадобиться вынести "страница и связанные с ней 10 страниц" в отдельный пакет, разрабатываемый отдельной командой, но это хорошо ложится в монорепо, и плохо ложится в микрофронт.
Я не про fsd сейчас, просто зацепился за сам концепт)
"получаем просто монстра, с которым не понятно, как работать" - возможно это и цель. На Хабре очень много статей про то, что "не сделаешь баг - долго не проработаешь", "не сделаешь сложный код - станешь заменим". Это даже пыталось стать трендом, и ряд проектов специально берет нишевые подходы, чтобы долго думать что такое виджеты, энтити и фичи, хотя ни в бизнес-требованиях, ни в логике приложения этого нет.
Я безусловно за классическую структуру проекта, так же как и за семантическое отношение к написанию кода - они должны быть читаемыми и логичными, как предложения, и так же логично располагаться в проекте. Но по сложно понимаемым причинам ряд разработчиков идет сложным путем, возможно как раз из-за "авторитетности" мнений и выбирает даже Redux или что похуже.
Мутабельность можно сделать и с помощью других паттернов, прокси вовсе не обязательны. Главное - интерфейс, что при мутации значения происходит ререндер. Что не нужно делать ни useReducer, ни setValue. И жесткие ограничения на интеграцию с другими системами реактивности Реакт тоже не красят - намерение плотно привязать на его экосистему и его подходы как раз и мешают более эффективным решениям "стать общепринятым стандартом". Как бы сообщество ни хотело исправить это - получаем только новую пачку хуков, решающих проблемы, которых и не должно было бы быть.
Статья просто пронизана болью, даже выводы дочитать не смог. Болью веб-студии, которой попадаются классические заказчики, которые не знают, что им нужно, и не понимают сложности внедрения и изменения функционала. Будто вернулся на 10 лет назад, когда тоже в такой конторе работал) К сожалению, это очень неблагодарная и рисковая сфера как для бизнеса, так и для разработчиков. И научить всех клиентов "сформировать план работ и выстроить грамотную коммуникацию" точно не получится. Остается посочувствовать и пожелать удачи.
Это повод сказать, что Реакт очень медленно развивается, и этот релиз - в основном работа над ошибками, а не большой шаг вперед. Есть библиотеки как mobx, которые уже давно продвинули его на несколько шагов вперед, сделав удобный механизм работы со стейтом, и оставив Реакт только для рендера компонентов.
Я думаю, что именно на этом Реакт и должен концентрироваться, а не перерастать в большую неэффективную систему, пробуя на аудитории новые подходы в виде хуков (функции в функции с сайд-эффектами и невидимым кешем) со строгими правилами (или не очень строгими, а очень странными, как в случае с новым use) или серверные компоненты.
К сожалению,
state.props = props;
очень не понравится mobx, если вMyComponentLocalState
будет зависимость от этих пропов или геттеры - скажет, что синхронно менять значения при рендере нельзя. Поэтому придется использовать useEffectСпасибо за статью. Под "в этот раз опирались на более объективные критерии, и выбор был очевидным", как понимаю, имеется в виду MobX? Если так - могу посоветовать пару удобных подходов mobx-stateful-fn и mobx-use-store - они дают очень удобные механизмы. Там же рядом и неплохой роутер, вдруг пригодится)
Согласен, главный минус - малое коммьюнити и количество библиотек. В реальных проектах нужен очень большой выбор библиотек, потому что все с нуля писать - не только занимает много времени (даже для селектов библиотеки пишутся годами из-за совместимости с большим количеством браузеров), но и требует намного более тщательной поддержки, что усложняет проекты. То есть упрощение написания функционала в SolidJS по сравнению с React становится незначительным по сравнению с тем, что нужно самостоятельно реализовывать на порядки больше компонентов.
Но библиотека хорошая, спасибо за статью.
Почему плохо - я привел несколько аргументов в комментарии выше.
Про сильную связанность с view, про сложность с SSR, про невозможность вызова вне Реакта (например до рендеринга приложения или в ответ на какую-то реакцию), про сложности генерации реалтаймовых валидаторов, про отсутствие возможности протестировать вне компонентов, про слабую связь со стором (например для вывода нотификаций).
Как хорошо - делать независимый от фреймворка слой апи, при необходимости передавая в него разные сущности типа сторов, экшенов, локализации. Пример я приводил здесь https://github.com/mobxjs-ru/api-by-config
На мой взгляд, сокращение имен классов - это экономия на спичках в ущерб DX.
При сжатии gzip/brotli экономия на большой html будет в байтах, максимум - в паре килобайт. Это не повлияет на оценку Lighthouse и на пользовательский опыт, если вы не гонитесь за тысячными долями процента перфоманса
При чтении дерева элементов в инспекторе видим несемантичные
j1 g83
классы, соответственно как найти место в коде, в котором нужно поправить стиль? Можно переключиться на React Dev Tools, выбрать элемент, потом обратно в инспектор, проверить там новые стили, и дальше - копать код проекта, перебирая папки и файлы в рандомном порядке. Проблема усугубляется тем больше, чем больше разработчиков в компании, и чем хуже знаешь кодовую базу проекта. Частично проблема решится CSS maps, но тоже много лишних телодвижений.При этом семантичные CSS Modules сразу покажут где находится файл -
src-components-layouts-userMenu src-pages-user
.При регистрации ошибки в Sentry например, разработчик смотрит путь, который пользователь накликал (в Сентри это логируется). И в случае сокращенных имен он увидит
click[.j1]
- как с этим работать? Скорость дебага значительно увеличится.Для автотестов стабильные имена
[path][name]__[local]--[hash:base64:5]
можно использовать как уникальные идентификаторы элементов в ряде случаев и обойтись без data-test-id или id (если не учитывать в селекторе hash). Также можно дать не сильно разбирающемуся в автотестах сотруднику визуальный инструмент накликивания пользовательских сценариев. Тогда он передаст разработчику семантичный список действийclick[.src-components-layouts-userMenu]
и разработчик при необходимости очень быстро добавит id либо оставит селекторы в оригинальном виде, если эти классы достаточно специфичны.Итого - сокращением названий классов мы улучшили пользовательский опыт на неизмеримо малую долю, при этом кардинально усложнили работу верстальщикам, разработчикам, тестировщикам и в целом тратим больше денег компании каждый день.
Должен быть контект для решения задачи. "Наш продукт имеет систему виджетов, которые могут получать данные из разных источников, поэтому рассмотрим вариант когда url для фетча передается в компонент из внешнего источника". Дальше - "допустим, вы решили делать фетчинг через кастомный хук, расскажите, какие минусы и плюсы у этого решения и как бы вы это реализовали". Вот это была бы достойная задача на собеседование, проверяющая глубину опыта. А без контекста - это конь в вакууме, проверяющий читали ли вы документацию Реакта и fetch, чтобы проверить самую базовую базу
Ну вот позвольте рассказать, как я бы воспринял эту задачу, чтобы объяснить мою позицию.
Я работал в десятке компаний и сделал больше сотни сайтов, половина - на Реакте. Каждый день вырабатывал практики, как организовать слои приложения так, чтобы они были одновременно независимыми и при этом легко интегрировались друг в друга.
Я ожидаю, что на собеседовании проверят не то, как я различаю == и ===, и не чему будет равно [] == true, а проверят реальный опыт на реальных задачах, чтобы я смог показать глубину опыта.
Мне дают на собеседовании задачу про "компонент получает url", на что я сразу напрягаюсь, "компонент функциональный", на что соответственно сразу думаю про SSR, "хук, которых фетчит данные", на что сразу думаю - куда я попал?
Первое, что я отвечу - так делать нельзя и только джуны могут так делать. Расскажу про все, что перечислил в первом посте, займет минут 20. У собеседующего время ограничено, он не проверил мое "знание хуков Реакта", но получил море как бы не связанной с изначальным намерением информации.
Адекватный собеседующий скажет "тогда давайте заменим получение данных по апи просто на асинхронную функцию", и тогда я сразу соглашуть написать то решение, которое приведено в статье - с зависимостями во втором аргументе useEffect и с возвращением псевдо-прерывающей функции, чтобы при размаунте результат не обрабатывался. Конечно, добавил бы про обсерваблы, про варианты обхода ручного (!cancelled), про унификацию обработки, про типизацию ошибок, про коннект к разным частям приложения, про слои и т.п. еще на час беседы, но т.к. время ограничено и я четко понял, что задачей хотят проверить "понимание основ React", то опустил бы.
При этом внутренне я, конечно, ругнулся бы - потому что спросили бы что-то более приближенное к реальности, а не пример из доки Реакта, который все 100 раз читали.
В итоге - мы оба потратили много времени на обсуждение организации апи и по итогу часа я выполнил 1 задачу из 3. Собеседующий так и записал, пришло письмо от HR "вы нам не подходите". История не особо выдуманная, на опыте было немало такого) Может, это мои заморочки, что не готов обсуждать совсем базовые штуки (шутка про Яндекс, в который я прошел около 20 собесов в разное время, чтобы увидеть какая дичь у них в коде), но и на джуна я не собеседуюсь - а такие вопросы тем не менее в каждой первой компании.
Поэтому для экономии времени всех и для получения специалистов повыше джуна задача из статьи не подходит, даже если интервьюер супер-адекватный и готов зачесть разговор на час за решение всех задач.
Все так, для статьи "любимая задача про знание Реакт" придраться больше и не к чему, кроме как к тому, что интервьюеры "могут не обладать соответствующими знаниями". Но вот к "Вот такой вариант я считаю "идеальным"" придраться можно очень много к чему) потому что код в статье - это решение джуна, неприменимое к реальности
Да, как отправную точку - отлично, но в выводе статьи - "Задачка позволяет проверить, как кандидат понимает устройство рендеринга React...", то есть ответ "так нельзя" скорее всего приведет к тому, что интервьюер либо решит, что задачу кандидат не решил и не знает основ, либо не сможет проверить эти знания потому что под рукой нет более подходящей задачи. То есть все же считаю что правильно сказал - определенные разрабы отсекутся этой задачей, потому что она проверяет для интервьюера одно, а для соискателя может быть совсем другое (знание архитектур, а не setState и useEffect Реакта)
Вы же написали статью, чтобы другие люди пользовались в своей практике? И такие найдутся, которые будут считать что это задача на хуки, а кандидат думать что на апи - и разойдутся.