All streams
Search
Write a publication
Pull to refresh
47
0
Дмитрий Казаков @DmitryKazakov8

Front-end архитектор

Send message

Подсветка, автодополнение - да, сейчас решаются плагинами. Автоформатирование stylelint, проверка синтаксиса - их тоже уже подвезли? Если так, и для всех распространенных IDE + вариант через консольную команду для precommit и CI, то хорошо, этот пункт снимается.

Указав на пример styled(Button) вы проигнорировали пункт про omitProps - какие пропы должны передаваться в стили, а какие в компонент? А если в компоненте стоит { onClick, ...otherProps } = this.props, и вы забыли обернуть styled(Button) в omitProps(styled(Button), 'someStyleProp'), то будет ругань в TS, в консоли от неизвестного пропа для html + поломка гидрации SSR в ряде случаев. Очень много с этим сталкивался, под полсотни hotfix в master у меня из-за этого накопилось в тех проектах, которые использовали css-in-js.

Про извлечение в чанки с автосгенерированным именем тоже описал ряд консернов.

Зачем с этим всем бороться на пустом месте, когда альтернатива в виде CSS Modules лишена всех этих недостатков?

"Лично мне не нравятся повсеместные стрелочные функции и this" - странный аргумент для меня. Написание method = () => вместо method() и this сподвигли на то, чтобы выпустить новую утилиту, которая превращает функциональные компоненты Реакта в SolidJS-like? Но классы ведь придуманы для удобной организации кода. Чтобы не была функция из 5000 строк и 50 вложенных функций смешанного назначения, а были удобно организованные слои - хендлеры пользовательский событий, реакции, состояние, геттеры подготовленных данных, которые схлопываются IDE и гармонично могут использовать друг друга без заботы о hoisting, когда const a = 1 объявлена ниже, чем используется.

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

Думаю, будущее все-таки за классами, и то что разработчики реакта придумали с раздутыми функциями с необходимостью ручного проброса одного в другое, скрытыми хранилищами и ручной заботой о равенстве по ссылкам уйдет в прошлое. Хотя afc решает часть проблем, остальные остаются.

Однозначно палец вверх за статью, хороший заход из песочницы, но перфоманс в широком смысле - это меньшая из зол css-in-js. Можно вполне жить и с увеличенным временем сборки, и с тормозами в интерфейсе, и замедленной работой IDE и TS за счет новых ts-файлов. И даже с разметкой таблицы, где в style в каждой колонке вшиты стили и при SSR размер html-страницы за счет этого вырастает в разы, а найти элемент в разметке инспектора по стилям и потом в проекте - отдельная форма извращения, в том числе если эти стили автоматом выносятся в классы с md5-like наименованием. А даже с фичей вынесения классов редко какой css-in-js движок делает достойную оптимизацию типа CSSO, и не все умеют в полифиллы по browserslist.

Недостатков в dev experience намного больше. Начиная от отсутствия поддержки подсветки синтаксиса и автоформатирования внутри js-строк, продираясь через тонну компонентов Button / StyledButton, по которым не поймешь - стиль это или компонент, потом пролезая через море оберток в инспекторе Components из React Dev Tools и постоянной работы с omitProps, чтобы этот стилизованный компонент прокидывал все что нужно в собственно целевой реакт-компонент и TS не ругался, приходишь в итоге к "> * {}" для стилизации children. И можно считать себя счастливчиком, если при этом всем не пришлось допиливать функционал, который есть в любом препроцессоре из коробки...

Мне сложно понять, кто может использовать эту технологию в production, но почему-то статьи на хабре в пользу css-in-js до сих пор появляются, хотя их уже мало кто комментирует - слишком много уже это все обсуждали.

У Asus большие проблемы с поставками. Мечтал весь год об Asus ExpertCenter PN64 , вышел вроде в июне, в продаже до августа - 0 предложений. Потом появились в ряде магазинов (Германия, Швейцария, Сингапур), но куда ни писал - везде отвечают "поставщик не прислал нам товар, в наличии нет". До декабря мучался пытался заказать, в итоге взял MOREFINE M600 R9-6900HX, который сразу доступен был после анонса и быстро пришел (пока не запускал, жду комплектующих). Вполне вероятно, что с модификацией PN64 на Raptor Lake будет похожая история, поэтому лучше с осторожностью относиться к этой модели

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

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

Cypress, на мой взгляд, лучше подходит для интеграционных тестов - у него есть как режим прогона в открытом браузере, так и в headless режиме. Но это требует запуска проекта в отдельном процессе (либо докер-контейнере), на который уже заходит Cypress. Это усложняет настройку CI, но упрощает подготовку самих тестов - не нужно вызывать методы рендеринга в некий nodejs-DOM и надеяться, что в актуальных браузерах тоже будет работать, так как тест заходит на реальный запущенный сайт в реальном браузере.

С Кентом хотелось бы согласиться, и сложно не согласиться, что E2E это единственная осязаемая гарантия итоговой работоспособности, но найти компанию, которая потратит столько усилий для создания на каждый тест стабильных стендов со всей инфраструктурой бэка и базами с сэмпловыми данными в соответствии с текущей веткой каждого сервиса и структурой баз на определенный момент времени (либо версией, указанной вручную), мне пока не удалось. Особенно, если есть интеграции со внешними сервисами, ибо в каждом нужно сконфигурировать работу с данным тест-стендом. Были попытки на реальных стендах прогонять, создавая море новых юзеров и не завязываясь на стабильные сущности, но этим можно покрыть только функционал "котик отобразился" без конкретизации какой котик, в каком порядке, с какими характеристиками, какими действиями при клике и т.п., что надежности продукту не добавляет.

Я когда ни спрашиваю "хочет ли бизнес много некачественных фич", отвечают "хотим 30% усилий которые дают 70% результата". Эта сказка качует из ума одного менеджера к другому, дает им отчитаться про "мы внедрили вот это по плану 1 неделя вместо 1 месяца, супер-топ команда и метод управления". В реальности получается костыльное минимально рабочее решение + 70% техдолга, который в геометрической прогрессии растет и сначала тормозит дальнейшую разработку, а в скором времени - блокирует. Факт, что то что таким образом написано за год надо дорабатывать еще 2 года все гонят прочь, разработчики не могут разобраться в той каше, которую из себя представляет продукт и уходят, новые нанятые какое-то время мучаются и смиряются до тех пор, пока история не повторится, внедрение фич идет медленно, менеджеры нанимают еще разработчиков и затраты растут как снежный ком, денег не хватает, клиенты и инвесторы в ярости, компания рушится.

Стандартный кейс, который встречал практически везде, где бизнес гонится за скоростью и играет "в быструю", а не "в долгую". Ситуация особенно усугубляется, если используются велосипедные решения и их разработчики становятся ключевыми, тогда скорый исход проекта в небытие еще ускорится, сколько бы строк в таблице ни выводил этот фреймворк. Исходя из этой специфики я бы все равно придерживался популярных решений (React, Vue, Angular) - так хотя бы можно отсрочить кризис в компании. Но решить его может только сам бизнес, кардинально изменив модель требований и ожиданий.

Рынок подстраивается под популярные технологии, это нормально для всех отраслей - раньше нужны были кто работал лопатой, теперь экскаватором. Много курсов Реакта и Вью выпускают много потенциальных разработчиков. Есть временной лаг между этапами спрос-предложение-"предложение с опытом работы YYY", но не критичный на мой взгляд, и доносит до бизнеса идею что сотрудников нужно в том числе выращивать самостоятельно.

Поддерживаю. Архитектуры и проекты в целом в подавляющем большинстве одинаковые с небольшой в масштабах приложения спецификой (для того же получения данных - REST, RPC, Socket, binary, GraphQL), которая разруливается адаптерами. Целые массивные слои (роутинг, сборка, валидация, кодогенерация, темизация, локализация, механизмы SSR, BFF) с незначительными изменениями таскаю из конторы в контору с крайне различающимся бизнес-направлением (от банков до бирж), если сделать их достаточно гибкими и фреймворконезависимыми.

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

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

Сейчас да, сделать на них точечное обновление, ssr и нормальный проброс не только строковых значений - та еще задача. С массивами работать сложно, shadow dom концепт непростой... Проблем масса. Но как прототип для нативных кастомных элементов - пойдет. Мы же о будущем говорим, и я надеюсь что удобство jsx может придти и в стандарты

Спасибо за статью, интересно!

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

  • enterprise - продолжит тяготеть к отдельным слоям и фреймворконезависимости. Т.к. я занимаюсь в основном этой сферой, то давно привык собирать приложения "по кубикам", которые не зависят от фреймворков (сборка, роутинг, хранилища, компоненты с адаптерами для разных рендереров, апи, сайд-эффекты, слои реакций и т.п.) - это обеспечивает максимальную гибкость и предотвращает устаревание. На первых этапах (MVP) как правило используется один из мета-фреймворков, но не надолго.

  • малый бизнес - продолжит пользоваться no-code решениями и готовыми CMS

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

А в целом тренд сейчас идет на расширение нативного HTML API - те же веб-компоненты и воркеры для вычислений (чтобы не блокировать основной тред), пропоузал добавления типов в JS. Они постепенно вытесняют компоненты конкретных фреймворков, а фреймворки добавляют их поддержку. Самая очевидная проблема (SSR), думаю, постепенно решится, и мета-фреймворки следующих поколений будут основаны на них. И, надеюсь, функциональные компоненты наконец уйдут в прошлое, уступив классовым)

Кстати по поводу установки зависимостей в CI - они как правило меняются не часто, и можно сделать кеш слоев именно в CI

#13 [ 5/10] COPY pnpm-lock.yaml /app/
#13 CACHED
#14 [ 7/10] RUN pnpm install --prod
#14 CACHED

Так будет упираться только в скорость копирования в большинстве случаев, а это хорошо оптимизируется. Лишние 100мб дадут лишь несколько секунд сверху. Я не силен в девопсировании, но у нас в рецепте билда на восстановление из кеша и все сопутствующее уходит 1 минута + 30с сборка, так что увеличение размера node_modules влияет незначительно.

На реакте, да, но рефрешем не пользуюсь, поэтому тут не подскажу - в основном в проектах SSR (кстати тоже прекрасно работает в SWC), поэтому использую полную перезагрузку страницы. Если нужно поработать над компонентом - то какой-нибудь стайлгайд.

Когда я перевел сборку проекта (не тестов) с Babel на SWC получилось вот что. Верхние результаты с минификацией, которая тоже переехала с Terser на Terser.SWC. Также здесь включено море плагинов, которые остались без изменений, поэтому результат довольно чистый - изменился только транспайлер

(CI) Babel без прогретого кеша + Terser

#16 22.96 [WEBPACK] finished building node within 20.21 seconds
#16 40.69 [WEBPACK] finished building web within 36.324 seconds

(CI) SWC + Terser.SWC

#16 13.14 [WEBPACK] finished building node within 10.57 seconds
#16 29.60 [WEBPACK] finished building web within 24.831 seconds

(Локальная сборка без минификации) Babel без прогретого кеша

[WEBPACK] finished building node within 5.443 seconds
[WEBPACK] finished building web within 6.745 seconds

(Локальная сборка без минификации) Babel с прогретым кешем

[WEBPACK] finished building node within 2.348 seconds
[WEBPACK] finished building web within 4.044 seconds

(Локальная сборка без минификации) SWC

[WEBPACK] finished building node within 1.609 seconds
[WEBPACK] finished building web within 3.742 seconds

Все на одной машине, запуски делал раз по 5, среднее значение скорости менялось не сильно. Выводы получились следующие:

  • SWC не требует прогревания кеша, что максимально отражается в CI

  • Terser.SWC - очень быстрый минификатор, без него результаты не такие поразительные

  • Переезд на SWC получился почти безболезненный (да, его нужно настроить и подобрать альтернативные плагины, как у babel, но мои кейсы с недавним добавлением swc-loadable-components закрылись полностью). Из негативных моментов - нет плагина для оптимизации lodash и сахарные декораторы SWC несовместимы с некоторыми библиотеками, поэтому пришлось переписать их на функциональные.

  • Размер node_modules действительно вырос, но цифры у меня не сохранились.

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

Про "никто не меняет вью" в принципе согласен, но я скорее для спортивного интереса поддерживаю архитектурные части изолированными и абстрагированными. Не только Реакт могу поменять на Preact/Inferno/SolidJS и экспериментирую с заменой через адаптеры на другие рендереры (даже веб-компоненты), но и MobX на другие реактивные библиотеки, тот же Solid mutableStore. Это позволяет мне лично расти как специалисту и создавать долгоподдерживаемые проекты, заодно сравнивая перфоманс и набор фичей и оттачивая дизайн системы. И в целом стараюсь не завязываться на конкретные особенности (ex. версия определенной либы, которая рендерит асинхронно). И другие архитектурные части тоже делаю самодостаточными и независимыми. Поэтому хаки чисто для MobX в виде асинхронного автобатчинга не рассматриваю.

Конечно, можно и с ним жить, и утилиты подбить так, что будет и SSR, и нормальный стек вызовов, и соблюдена очередность реакций. Можно и на альтернативные библиотеки наворачивать такой функционал, но все равно мне ближе явный подход - где изменяется стор, оборачивать в batch (runInAction в mobx). Так добавляется бойлерплейт, но упрощается понимание за счет синхронности операций в и так непростом асинхронном мире JS, а в ряде случаев - улучшается перфоманс (незначительно, но в перегруженных компонентами страницах асинхронщина может привести к глитчам).

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

И еще - в асинхронной реакции может уже не быть той сущности, с которой она должна работать (компонент размаунтился, this = undefined, состояние модульного стора очистилось).

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

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

  2. Цепочки авторанов (в первом меняется параметр, который слушает второй авторан, в третьем прослушивается результат второго) - хоть Реакт 18 и добавил асинхронности в рендер, я на это не полагаюсь, и на каждый рендер подготавливаю финальное состояние, чтобы отрендерилось 1 раз. Т.к. ориентируюсь на более-менее свободную замену частей приложения (в том числе рендерера типа Реакта), то важно синхронно подготовить данные для избежания ререндеров - это просто хороший тон и предотвращение проблем.

  3. Ну и минорное замечание к решению выше - если подменять одну функцию другой и передавать в setTimeout(fn) то контекст теряется и в стеке вызовов будет batch вместо реального имени функции. Фиксится парой строк, но все равно об этом думать надо

Оказывается, это Mazaa шифруется под новым ником, интересно)

В реальных задачах я могу вешать 5 авторанов последовательно, и даже если меняется их порядок - то где-то работает некорректно. Конечно, это антипаттерн и нужны прямые руки и продумывать лучше, но как это всегда бывает - нужно все срочно и потоки МР по 5-10к строк ежедневные, вангую, что с асинхронным вызовом было бы проблем больше, чем порядок поменять.

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

  configure({
    reactionScheduler: f => setTimeout(f, 1)
  });

Тоже пользуюсь Find Usages, дебаг очень простой. А чтобы не было ворнингов все эшены автоматом оборачиваются в action, а для батчинга runInAction, если после асинхронных вызовов. Я пока не считаю, что оправданно вырубать enforceActions - многое в коде зависит от батчинга (мне нужно чтобы несколько переменных менялись сразу, а не постепенно - не для рендера в Реакте, а для autorun, которые например отслеживают форму - надо чтобы и value поменялись, и isFocused, и валидаторы, и авторан вызвался 1 раз с финальными данными). И таких ситуаций много. Плюс я стараюсь делать библиотеко- и фреймворко-независимую архитектуру, а в альтернативах MobX тоже используются колбэки системной функции для батчинга, иначе его просто не выстроить синхронно.

К теме дебага - все экшены у меня еще логируются в визуальный интерфейс (история вызова + время на исполнение) в виде stack bars chart, что максимально сужает поиск по Find Usages.

Немного вклинюсь по поводу юзстора - решается оберткой (я пишу на классовых компонентах, поэтому пример для них)

import { observer } from 'mobx-react';
import { Component } from 'react';

import { TypeGlobals } from 'models';

import { StoreContext } from './StoreContext';

export class ConnectedComponent<TProps = any, TRoute = never> extends Component<TProps, any> {
  // SSR not able to recalculate components on observable updates,
  // so on server side it has to be rendered twice
  static observer = IS_CLIENT ? observer : (someComponent: any) => someComponent;

  // Describe context, so no boilerplate in components needed
  static context: TypeGlobals;
  static contextType = StoreContext;
  declare context: TypeGlobals;
}

// Any component

class AnyComponent extends ConnectedComponent<TypeProps> {
  render() {
    this.context // Perfectly typed and reactive, no boilerplate at all
    this.props // Perfectly typed and reactive, no boilerplate at all
  }
}

В Webpack делаю транформер, который все компоненты, которые экстендят ConnectedComponent превращаются в

const AnyComponent = ConnectedComponent.observer(class AnyComponent...)

Так получается реактивность без сахарных декораторов (чтобы использовать не Babel, а более шустрый SWC транспайлер), с отдельной логикой для SSR и без бойлерплейта.

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

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity