Обновить
4
Ghaechka@ghaechka

Fullstack web разработчица, преподаватель

Отправить сообщение

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

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

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

А ещё границы могут плавать - и тогда приходится переопределять саму базовую концепцию стора.

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

Вас такое наверное в ступор вводит?

Меня мало что в ступор вводит. Писать непродуманно — это распространенная ситуация, с которой я стараюсь бороться.

Главная из которых - решение поставленной бизнес-задачи в рамках доступных трудозатрат ;)

Отказ от контрактов и TS (который вы почему-то считаете избыточным) экономит время только первые две недели проекта. Мало того, что это потенциально увеличит трудозатраты в дальнейшем при добавлении новой сущности/фичи, так еще и изменение бэка повлечет за собой набор багов, которые вы запаритесь отлавливать по всему проекту.

Удивительным образом вы пытаетесь продать техдолг и отсутствие границ зон ответственности ради мнимого "прагматичного решения бизнес-задач".

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

Что-то не вижу того, что мой образец – идеальный. Вроде и не говорю, что это идеальное решение, которое всем подойдет. Вопрос в том, что в рефакторе нужно отталкиваться не столько от практик, сколько от того, чем является стор, для чего он предназначен, что с этими данными БЛ делает. И отсутствие этих границ — это выстрел в ногу. А пока что автор не особенно-то и рассматривает эти границы, хотя паттерны про изоляцию слоев и нормализацию данных не является оверинжинироингом.

Ad hominem – вещь, конечно, интересная, но ведь можно рассмотреть вопрос того, что у собеседника макбук. Это уже давно не аргумент в пользу ИИ, полагаю у вас нет других аргументов в это.

Что по сути.

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

На самом деле Алексу нужно задуматься вот о чем. Pinia прививает очень плохую практику, в которой стор "и швец и жнец и на дуде игрец", то есть он:

  • Инициализирует данные

  • Выполняет запрос к другим данным

  • Меняет собственное состояние

В идеале нужно было бы подойти с такой точки зрения:

  • Что в этом сторе является БЛ, а что – логикой стора

  • какие данные стор хранит и как их получает

  • как данные нормализуются при записи и какой тип является «контрактом» стора

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

  • какие вычисления являются производными от состояния стора (computed), а какие — бизнес-логикой, которой в сторе вообще не место

  • какой тип является контрактом стора — any вообще не должен быть допустим, Record<string, unknown> — полумера. Нужно использовать явный интерфейс и типизировать через него хранилище. Если не получается – значит проблему надо решать уровнем выше, до написания стора

  • внешние элементы, включая композабл, не должны менять состояние стора напрямую — только через явные методы. Подход get/set никто не отменял

И взглянув на старый код

export const usePropertyStore = defineStore('property', () => {
  const items = ref<Record<string, any>>({})
  const isFetching = ref(false)

  watch(
    items,
    (val) => {
      localStorage.setItem('items', JSON.stringify(val))
    },
    { deep: true }
  )

  const activeType = computed(() => localStorage.getItem('type'))
  const currentItem = computed(() => {
    if (!activeType.value) return null
    return items.value[activeType.value]
  })

  async function load() {
    isFetching.value = true
    const result = await fetchProperties()
    isFetching.value = false
    return result
  }

  return { items, isFetching, activeType, currentItem, load }
})

мы неожиданно поймем, что в сторе должно быть примерно так:

interface Property {
  typeId: string
  label: string
  // ... остальные поля
}

interface RawProperty {
  type_id: string
  name?: string
  // поля как приходят с API — snake_case, необязательные
}

export const usePropertyStore = defineStore('property', () => {
  const items = ref<Property[]>([])
  const activeType = ref<string | null>(null)

  const currentItem = computed(() => items.value.find(p => p.typeId === activeType.value) ?? null)

  function _normalize(raw: RawProperty[]): Property[] {
    return raw.map(p => ({
      typeId: p.type_id,
      label: p.name ?? 'Unknown',
      // остальные поля
    }))
  }

  function setItems(raw) {
    items.value = _normalize(raw)
  }

  return { items, setItems, activeType, currentItem }
})

find здесь не случаен — он явно выражает намерение: найди элемент по условию. Индексный доступ items[key] выражает другое: возьми по заранее построенному ключу, и тогда ты несёшь ответственность за консистентность этой структуры. Массив с find честнее: источник правды — список, поиск — производная операция. Если завтра условие усложнится (например, искать по typeId и regionId), массив расширяется тривиально, а Record потребует смены ключа и рефакторинга всех мест записи.

В идеале надо смотреть в сторону того, что стор может явно иметь функции для нормализации (чтобы привести входящие данные к контракту) и дальше только методы чтения/записи, использующие типы для того, чтобы извне сообщать структуру. RawProperty и Property могут и должны отличаться — граница между ними и есть зона ответственности нормализатора.

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

В целом в мобильных приложениях BFF сам по себе хорошо залетает, так что думаю что-то похожее можно затащить :) насколько я понимаю , р-нэтив нормально шаблонизируется

БэДэЮай тоже такое себе название, но называют же 😄

Давайте пойдем с конца, так как это фундамент.

1. Про мотивацию бэкендеров

Ваша гипотеза про «буст мотивации» разбивается о реальность. Мой опыт (в компаниях от 50 до 1000 человек) показывает обратное: современные бэкендеры (даже PHP-разработчики, которые исторически ближе к вебу) не хотят заниматься фронтендом — ни версткой, ни UX, ни изучением специфики браузеров.

В данном кейсе вводная от Тимлида (самого бэкендера) была жесткой: «Мы НЕ хотим писать фронт». Превращать профильных специалистов в фуллстаков поневоле — это прямой путь к выгоранию команды и потере качества на обеих сторонах.

2. Инфраструктура и стоимость

Вы предлагаете вместо JSON-контракта (который мы внедрили силами 1 фронта и 1 тимлида) развернуть оркестрацию Микрофронтендов. То есть задействовать DevOps, перестроить CI/CD и переучить всю команду?

Это колоссальный оверхед ради того, чтобы получить разрозненную логику на Alpine.js без нормальной типизации.

Для сравнения: онбординг PHP-разработчика в мою структуру JSON занимает 10 минут. Это прозрачный, типизированный контракт, который сразу дает результат.

3. UX и State на бэке

Мы строим ERP, аналог 1С. Пользователи требуют мгновенной отзывчивости, сравнимой с Excel.

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

Есть такое подозрение, что у вас есть убеждение, что фронтенд — это просто «формочки». Но современный фронтенд — это полноценное приложение, работающее в среде браузера (сродни разработке под iOS/Android).

Вы же не станете предлагать переносить логику рендера нативного мобильного приложения на бэкенд, чтобы оно просто отображало картинки? Здесь то же самое. Мы управляем сложным состоянием клиента, и JSON здесь — это идеальный протокол генерации структуры этого приложения.

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

«Дети с реакт»… кхм, даже интересно, в какую именно касту вы меня записали :)

По существу: HTMX — это лишь инструмент транспорта HTML через AJAX. Он не решает проблему сложной реактивности и состояния на клиенте (валидация на лету, зависимости полей, маски, работа с модалками без перезагрузки контекста). Для ERP-систем это критично.

Что до «бизнес-сущностей»: согласна, что карта — не есть территория. Однако в описанном подходе MDUI, JSON предоставляет проекцию правил сущности на UI: какие поля доступны, какие readonly, какие действия разрешены. В контексте задачи — это спор о семантике, не более.

Ну и главное. Возврат HTML (пусть даже через HTMX) означает, что нам снова придется верстать каждую страницу руками, просто теперь на стороне бэкенда (шаблонизаторы). А цель моей архитектуры была ровно обратной — уйти от ручного создания страниц к их генерации из структур данных. Бэкендеру проще отдать JSON, чем верстать <div> с классами.

Справедливое замечание, я видела подобные проекты.

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

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

За полгода «обрастания» не произошло. Если будет какая-то сложная логика - никто не пытается натягивать сову на глобус, просто рисуем нужное поведение.

Согласна, что с React Admin есть схожесть, паттерн может вполне его напоминать, хотя под капотом описан собственный движок, а не сторонняя библиотека.

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

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

Про «пользовательский опыт» — всё проще: бухгалтерам важна предсказуемость и скорость, так что жесткая унификация здесь только в плюс.

Спасибо за комментарий!

Ну проще винить тех, кто скупает твои любимые булочки…

Информация

В рейтинге
Не участвует
Откуда
Алматы (Алма-Ата), Алма-Атинская обл., Казахстан
Дата рождения
Зарегистрирована
Активность

Специализация

Фронтенд разработчик, Фулстек разработчик
Старший
От 3 200 $
Vue.js
JavaScript
TypeScript
Адаптивная верстка
Веб-разработка
Python
REST
ООП