Галоўная / Артыкулы

Дэт кранафлэя як выкалкі змян: дыагназа растучай базе коду React

Выучыце, як выявляць складныя рашэнні, якія робяць велікія прыкладнення на React, Redux, TypeScript і Next.js даскічнымі для змены, а таксама як поступова скасываць гэты дысконт.

Выдавец: REACTAPP.TOP18 хв чытання4156 слоў

Крыніцы: Frontend Technical Debt: The Problems Nobody Notices Until the React App Becomes Huge

Большыя прыемкі на React парадоксальна гэта роўна ніколі не становяцца складнымі для адтрымкі чырваю жоўтай прыемкі. Яны падупаюць за дапамою дужоўкі маленькіх, на першы погляд разумных рашэнняў: дадатковы useEffect, фрагмент стану Redux, які не патрэбаваўся ў глобальным формате, „тымчасовы“ компонент з 500 рэйсоў, спяльны віджет, які продовжвае атрымляць параметры, файл utils, які скаржы ў сябе все, што не мае явнага месца, типы API, прыклееныя да трох модуляў, правілы стайлізацыі, да якіх ніхто не смеяць вярнуцца. Ніякое з гэтых рашэнняў не выглядае некоректным у дзень, калі яго прыймаюць. Проблемы пачынаюцца, калі іх сотні накапліваюцца і начынаюць взаімадзейваць.

Гэта купліва ўскладненняў і є тым, што мы маем на увазе під тэхнічным дазволам фронтэнда, а галоўная ідея гэтага кансультатыву простая: найбольшыя проблэмы фронтэнда рэдка калі вызваныя адной вадзейным фрагментам коду, а радыя дробныя рашэнняя, якія накапліваюцца. Чытач знайдзе тут найбольш распашчастыя формы гэтага дазволу ў кодавых базах React, Redux, TypeScript і Next.js, прычыны, чаму кожная з іх стае дарогай у рэшэнні, а таксама практычны спосаб её зменшэння без перыявлення роботы над продуктам.

Як чыстая рэпазітарыя пачынае змінювацца

Новы проект зазвычай пачынаецца з такога макету, які кожны можа прачытаць за адну мінуту:

src/
  components/
  pages/
  hooks/
  services/
  store/

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

src/
  components/
    Button/
    Modal/
    Table/
    UserTable/
    UserTableNew/
    CommonTable/
    GenericTable/
    SharedTable/
    ...
  hooks/
    useUser.ts
    useUsers.ts
    useUserData.ts
    useFetchUser.ts
    useUserDetails.ts
    ...
  utils/
    helpers.ts
    common.ts
    misc.ts
    formatter.ts
    newHelpers.ts
    oldHelpers.ts
  store/
    user/
    users/
    userData/
    userProfile/
    ...

Зверніце увагу на патэрн: калькі таблиц, якія выпалююць адночасна функцыя, пяць хуків, якія всі запрашваюць „пользователя“ разлічнымі способамі, файлы-дапаможнікі, якія адрэсуюцца толькі словамі „new“ і „old“, а таксама чатыры папкі для таго, што можна вважаць аднам домэном. Ніхто гэта не спроектаваў. Гэта раслівалася, пашто ўсё час прыходзілі новыя запросы, і самэ гэта ўсё час робіць такія „боргі“ апаснымі: ніколі не бывае чыстаўкі моманту, калі стае ясна, што ўсё трэба пасправіць.

Штуткі, якія тыха становяцца постояннымі

Найболей звычны выказанні такога „боргу“ — адкладзена чыстка. Функцыя должна быць выпускана сёння; адпаведная реалізацыя займе два дні, а швальная — тры гадзіны. Выбор версіі, якая займае тры гадзіны, не значыць автоматычна паслабленую інжынерную практыку. Інодзе гэта проста правильны бізнесовы рашэння.

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

"Давай проста падключымо гэта да існуючага компонента." "Мы перерабім гэта пасля выпуску." "Пакуль давай залишым гэта ў Redux." ">Мы можамы яго узагальніць пазней." ">Нічога іншага ніколі не будзе патрабаваць гэтага."

Чырвоны месяц пасля — контекст знік. Ніхто не памятае, чымі саме кераваліся, калі пісалі гэты код; чалавек, які яго напісаў, можа быць перайшоў у іншую каманду; патрабавання, якім ён служыў, змянілася, а тры іншыя функцыі тепер залежаць ад яго працы. Ёжэльшчы трыгодзінны штучны спосаб працы можа зайняць тры недзелі.

Калі адны компонент керуе цэлай функцыяй

«Жорстка структура» — адзін з найболей вядомых симптамаў. Яна часта пачынаецца з разумна выглядаючага спісу задач, запісаных у адной функцыі:

function Checkout() {
  // fetch products
  // validate coupon
  // calculate totals
  // manage address
  // handle payment
  // track analytics
  // show modals
  // handle errors
  // render UI
}

Кожная новая вясковка падае ў тое ж месца, калі там уже знаходзится адпаведны код. У канцы канцоў такая структура адпавядае за:

  • выконанне запытоў да API
  • правіла бізнесу і перакананне данных
  • кераванне станам
  • аналітыку і перакананне прав
  • навігацыю і обробку адзінакоў
  • стылізацыю і атрыбуты відображэння

У гэтым случае React насправды не він ў вінаватасці. Асалідны прычына — занадта многа абавясакоў распрастарыліся за межы компонента. Компонент — гэта хораша адзінка для атрыбутавання дадзеных і выканання задач, якія належаць да таго частка інтерфейсу; ён не павінен стаць контэйнерам для всіх аспектаў, з якімі мае супакоўвацца функцыя. Правіла цэнавах, перакананне купонам і аналітыка можу знаходзіцца ў звычных модулях або спецыяльных хукі, якія можна тэставаць без выканання жадных дзейсненняў.

Павторна викорыстоўваныя компоненты, якія коштаюць больш, чым зарабляюць

На пачатку кар’еры павторнае викорыстоўванне здаецца беззаперечным прынтам. Але з падаўжэнням дазвяльнення стае ясна, што кожная абстракцыя мае свой цэн. Разглядзім таблыцу, якая была зроблена „налаштавальнай“:

<DataTable
  data={data}
  columns={columns}
  loading={loading}
  selectable
  searchable
  sortable
  pagination
  serverPagination
  expandable
  virtualized
  stickyHeader
  renderEmptyState={...}
  renderLoadingState={...}
  onRowClick={...}
/>

У тэхнічным сэнсе ён можа быть перыякты. Чы гэта будзе падтрымванае — гэта адзін з іншых пытанняў, а чыстая адказ — «залежыць». Кожны булінавый флаг удвойвае колькість комбінацыяў, якія должен падтрымваць компонент, а кожны новы корыстнік, якому патрэбна незначная змянена версія, дадае ўтварэнне ў виглядзе атрыбута заместо новага компонента.

Болей корыстны тэст, чым «чы можна яго перыякты?», — гэта чы абстракцыя спакойна дапаможа стварыць наступную функцыяльнасць. Компонент, які мае трыдзцать атрыбутаў, часта ўжо не являецца перыяктымі архітектурным рашэнням; гэта кальколька адзінаковых компонентаў, з’едыненых пад адной API. Композіцыя, калі корыстнікі складаюць меньшыя часткі, часта ёсць лепшым выходам, як пісана ў стацыі пра выправленне перанасылкі атрыбутаў у React за дапамогою композіцыі і слотаў.

Дзе папка utils хавае ваш домэн

Практычна кожны большы фронтэнд стварае папку utils, і спачатку гэта нешкодна. З часам аднойчы ён стае такім:

utils/
  formatDate.ts
  formatCurrency.ts
  userHelpers.ts
  permissions.ts
  productHelpers.ts
  validation.ts
  apiHelpers.ts
  stringUtils.ts
  common.ts
  helpers.ts

Калі папка доходзіць да такога стану, ніхто не можа з’ясаваць, дзе патрабуецца новая логіка, таму наступны функцыял падае ў common.ts, а потым і наступны. Код арганізаваны па типу файлоў, але бізнес-домэн залишаецца невидным. Перакананні ў правах, правілы продукту і аперацыі з API стояць па боку ад форматавання страк.

Кращы вопыт не ў тым, "У які файл з функцыяламі гэта патрабуецца?", а ў тым, "Якая бізнес-функцыянальнасць керуе гэтай логікай?" Функцыянальная структура дапамагае чытка адпаведзець на гэты вопыт:

features/
  checkout/
    components/
    hooks/
    services/
    types/
    utils/

products/
    components/
    hooks/
    services/
    types/
payments/
    components/
    hooks/
    services/
    types/

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

Redux як універсальны хранар

Redux — адна з справядліва корыстных інструментоў, але размешчэнне всього ў глобальным стаце — адна з найшырэйшых дзяўаў для стварэння «боргу» у фронтэндзе. Хранар зазвычай пачынаецца з частак, якія явна паслугуюць глобальнам вжыванню:

auth
user
permissions
cart
products
checkout
notifications

Потым ён прабірае тыя элементы, якія насправды стосуюцься адной экранаўкі чы аднай взаімадзея.

modal
filters
selectedTab
searchText
formState
temporaryUIState

У канцэ ўтрата паводзіцца як база дадзеных для всей аплікацыі, дзе можа зберагвацца ўсё тое, што два компоненты могу захацець прачытаць. Тэхнічна будзь-якае значэнне можа быць дадзенаў у Redux, таму пытанне „Можна лі гэта?“ — некоректнае. Правильныя пытанні — чы рэальна стану неабходнае ў глобальным формате, і чы розпад стану на тры категоріі дапамагае.

Стан UI, які должен застацца локальным

Такія значэнні зазвычай належаць пры самом компоненте, які іх выкарыстоўвае, у useState або useReducer:

isModalOpen
activeTab
inputValue
hoveredItem
expandedRow

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

Стан сервера, які выкарыстоўвае стратэгію кэшавання

Данные, якія належаць бэкенду, маюць іншыя патрабаванні:

products
orders
users
invoices

Ёсць такі складныя проблемы, як кэшаванне, адзюнікацыя, паўтарны запрашоўванне дадзеных і ўневалідаванне. Їм трэбаецца цярпячы падход да запрашоўвання дадзеных (спецыяльная бібліятэка для керування станам сервера або адпаведны патэрн), а не ручна напісаныя функцыі, якія копіююць адпаведныя рэспансы ў базу дадзеных. Варыянты рашэння будуць дэтальна розглядзаныя ў стацыі пра перэосмысленне стану сервера ў большых дапрацоўках з React Query і Redux.

Стан, які ўсё ж такі є аб’ектам усей дапрацоўкі

Дзеяныя проблемы даслівна стосуюцца всей продукцыі:

authentication
permissions
application-wide preferences
cross-feature workflows

Этыя варыянты є разумнымі кандыдатамі на ролю глобальнай базы дадзеных. Урок не ў тым, каб "эвітаваць Redux"; ён у тым, што сама доступнасць не є прычыной робіць стан глобальным.

Боргі ў TypeScript: типы, якія перастаюць мець значэнне

TypeScript ўваходзіць у число найцэнныях інструментаў для большых фронтэндоў, але ён не стварае базу коду за вас. Працэгальна можна выкарыстоўваць гэты патэрн у всій аплікацыі і пры тым заставаць проект з типамі:

const data: any = response.data;

Кожны any выключае перакантрольванне самэлькі тады, калі данні прабываюць у аплікацыю, таму кампайляр не можа павесті вас пра змяну структуры API. Іншая распашчастая проблема — калі той самы тип домэна ўказваецца незалежна ў калькі файлах:

type User = {
  id: string;
  name: string;
}

Якща такое становішча ўтвараецца у калькі каманд, то выходзяць сяроднія дублікаты:

User
UserType
UserModel
UserResponse
UserDTO
UserData

Разработчыкі потым витрачаюць больш часу на выбор таго типу, які трэба імпортаваць, чым на выгоду ад системы типаў. Старанне прагненне да таго, каб «кожны элемент меў тип», не дапамагае досягнуць меты. Мэтая — каб важлівыя элементы системы, такія як адпаведзі AI, ентыты домэны і інтерфейсы компанентаў, былі чыста апісаныя і визначаныя ўсёраз. Хорашы TypeScript ускладнюе представленне некоректных станоў; паслаблы TypeScript проста стварае больш файлоў. Корыстным падзелам є выкарыстанне транспортнага типу для таго, што вяртае AI, і домэннага типу для таго, з чым працюе UI, пры чым яны перакладваюцца ў аднам месцы, замест таго каб было шасць слаба пов’язаных варіянтав.

Калі useEffect стае стандартным выходам з проблем

Эфекты ўсё таксама являюцца частым джерелам поступовых проблем. Усё пачынаецца з аднаго эфекту, які реагуе на адзін значэнне:

useEffect(() => {
  // do something
}, [something]);

Потым другі эфект падтрымлівае синхроннасць двух значэнняў:

useEffect(() => {
  // synchronize something
}, [a, b]);

Тады трэці элемент існуе галоўная метаю – паспрабаваць выправіць прыменшэнне падзею ад пярвага:

useEffect(() => {
  // fix previous effect
}, [data]);

Компонент ператвараецца на ланцюг эфектаў, якія запускаюць іншыя эфекты; гэта складна для разумення і спрыяе зайвым атрыбутам рэндараў і багам у парадку выконання. На гэтым этапе ўмеласць працаваць з React завісіць менш ад знанняў API, а больш ад чыстага разумення таго, як даны праходзяць через компонент.

Тыповы прыклад – зберагчэнне выведзеных даных у стацыі і сінхронізацыя іх з эфектам:

const [fullName, setFullName] = useState('');

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);

Гэта спачатку рэндаруецца з застарелым порожнім строкам, пасля чаго запускаецца эфект, а потым зноў рэндаруецца. Значэнне можна проста вылічыць пад час рэндараў:

const fullName = `${firstName} ${lastName}`;

Здаецца, гэта трывіальная разліка, але ў масштабах всей аплікацыі такія маленькія выборы накапліваюцца. Функцыі useEffect прызначаны для сінхронізаціі з системамі за межами React, такімі як падпісы, таймеры чыста DOM, а не для отрымання однаго стану з іншага. Болей шырокі аналіз прадстаўлены ў стацыі пра тое, як перастаць сінхронізаваць стан з useEffect.

Граніца сервера/кліента ў Next.js

Сучасны Next.js даў новы вімер архітэктурным рашэнням. Працуячы над тым, калькі компонента будзе атрыбутацыйнае ўмест, трэба таксама вярнуцца да пытання, дзе будзе выконваліся код: на серверы, на кліянцы чыста і таму, і тут. Сама гэта граніца ёсць архітэктурным выборам.

Частыя памылкі — гэта ператварэнне велікай часткі структуры ў кліентскія компоненты толькі таму, што аднаму маленькаму элементу патрэбна інтерактыўнасць. Наказ павінен стаць у верхней часты файлу сторункі:

"use client";

І ця вся сторанка тепер адаптуюцца для браузера як JavaScript на стороне кліента:

export default function ProductPage() {
  // entire page becomes client-side
}

Усё гэта таму, што адзінны кантактны элемент патрабуе частку локальнага стану:

const [isOpen, setIsOpen] = useState(false);

Гэта зручна, але такая зручнасць у адзінай частцы мае наследкі ў іншых аспектах: больш JavaScript адправляецца да браузера, а запрашэння дадзеных, якія моглі застацца на сервере, тепер павінны выконваны на стороне кліента. У тэрмінах App Router, усё, што імпортуецца з файла "use client", становіцца часткай пакета кліента, таму распаложэнне дырективы вплывае набагато больш, чым толькі на той компонент, які вы хацелі зробіць інтерактыўным.

Корыстна думаць па шарах, калі інтерактыўнасць перадаецца як можно нижэй у іерархіі:

Server
   ↓
Data fetching
   ↓
Feature boundary
   ↓
Interactive client component
   ↓
Small local state

заместо простага спрощэння:

Everything → Client Component

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

Дэт каштоўнасці стайлізацыі застаецца дэтам

Команды часта спрыяжуць тэхнічны дэт з проблемамі JavaScript і TypeScript. Стайлізацыя таксама стварае дэт, особліва ў продуктах з давгай історыяй, якія адразу викорыстоўваюць калькі падходаў: CSS-in-JS і styled-components, CSS-модулі, класы-функцыі, старыя глобальныя шаблоны стайлаў і адночасна калькі дызайн-сістэм. Раней чы пазней бібліятэка компонентаў будзе мяць ў сабе кашто-тапоўнейшае:

Button
ButtonV2
PrimaryButton
NewButton
StyledButton
CommonButton
LegacyButton

і ніхто не можа сказаць, якая кнопка є правильной. Тэхналогія стайлізацыі рэдка бывае асляпным прычынам. Асалоджаная проблема — гэта нехватка адпаведальнасці за дызайн-сістэму і чыстых меж компонентаў. Зрошчаны фронтэнд мае адказы на такія пытанні, як:

  • Калі компаненты ўскладневаюць публічны, спадзяльваны набор?
  • Калі з іх існуюць толькі для аднаго функцыяналу?
  • Калі каманда несе адпаведную адказнасць за кожны з іх?
  • Які ўзгаджаны спосаб прыўнесення варіянту?
  • Як адбываецца впрыўненне змян, якія парадзуюць проблемамі?
  • Што падпадае пад крэтырыя для включэння ў систему дизайна?
  • Што павінна застацца локальной і ніколі не быць пераведенаючы ў спадзяльваную?

Калі дуплікацыя краща за абстракцыю

Гэты урок часта ўскладніваеся для апытных разработчыкаў. Падазроўваю, што існуюць два компаненты, якія зараз выглядаюць прыблізна на 90% аднакова:

ComponentA
ComponentB

Інстынкт вельмі часта прабуе з’еднаць іх у адны:

<UniversalComponent />

Адзін раз дуплікацыя інодзе ёсць дышэйшым выборам, таму што яшчэ не вядома, чы розныя часткі будуць развівацца ў тым жа напрамку. Якщо яны розходзяцца, з’едынены компонент заполняецца умовнымі оператары, і кожная змяна ў аднай часткі рызыкуе нарабіць проблемы ў іншай. Некалькі дуплікацый — гэта нормальна ситуацыя; прытрымлівая абстракцыя можа стварыць зв’язкі, якія будуць існаваць гады.

Практычны стандарт: запісаць код два разы, вывучыць шаблон, абстрагаваць толькі тады, калі трэці варіант выкарыстоўвання падтвердзіць правільную структуру. Гэта не універсальны закон, але гэта разумная правила для пачатку.

Залежнасці, якія больш не маюць права на існаванне

Большы фронтэнды быстра накапліваюць залежнасці. Кодбаза, якой п’яць гадоў, може легка міткаваць бібліятэкі для:

  • стану, форм і пераканання данных
  • дат, графікаў і анімацый
  • комплектаў UI і стайлізавання
  • зямлеўвання данных
  • тэставання
  • Універсальныя інструменты
  • Дзеяны з іх часткай практычна не выкарыстоўваюцца, а іншыя былі замененыя функцыямі прыглядача або можлівасцямі, якія ўжо є частью фрэймворка. Адключыць хоць які з іх выглядае рызыкавана, таму яны застаюцца, і маніфест паступова ператвараецца на дакумент архітектуры, які ніхто не хоча чытаць:

    package.json
    

    Підтрымка залежнасцяў — гэта не толькі прыменэнне аптаваканаў безпекі. Гэта таксама перыядычны аналіз таго, чы розумеецца існаванне кожной залежнасці, вяртаючыся да выкарыстоўваных на ёе падтрымку ресурсаў, апгрэйдаў і іншых зусібоў.

    Рэальная цена: медленнейшыя, рызыкаванейшыя змены

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

    Feature request
          ↓
    Small change
          ↓
    Tests
          ↓
    Deploy
    

    У аплікацыі з вялікай кантэнтой залежнасцяў той самы запит ператвараецца на дзейство з аналізам:

    Feature request
          ↓
    Find where this behavior lives
          ↓
    Understand 5-year-old code
          ↓
    Check Redux dependencies
          ↓
    Check shared component
          ↓
    Check side effects
          ↓
    Check styling overrides
          ↓
    Change code
          ↓
    Something else breaks
          ↓
    Fix regression
          ↓
    Add another workaround
    

    Кожны крок у той другай ланцуговай структуре — это часы, які витрачаюцца на розумеўленне, а не на стварэнне, і саме последнія два крокі є ключовымі: змена нарабляе проблемы у чым-небудзь несувязанам, а патэчка додае ўжо ўсё новы спосаб працы. Гэта і є тое, калі база коду «нарабляе відсоткі», і гэта ўласна сутніця тэхнічнага боргу.

    Чаму фраза «всё ўсё ж працюе» є вводзячай у глухое ці загрозай сігналам

    Програма можа працаваць без дыяктароў, незважаючы на вельмі вялікі тэхнічны борг. Старонкі завантажаюцца, корыстувачы заходзяць у систему, замовленні обробляюцца, панель керування адображаецца, а статус CI — зелены, таму всі думаюць, што код у хорашым стане. Але потым з’являецца новая функцыя, і, як выглядае, проста змена захопляе два тыяжы. Самэ гэтай момент, калі борг стае відчутным.

    „Чы гэты прыкладнік работае?“ — гэта ўсё-такі слабы спосаб працэўнай перапрацоўкі. Наблыжэй да практычнай запитанне, ўсё бол важлівая калі системы растуць, ёсць: насколькі дорога будзе безпечна змяніць гэты прыкладнік?

    Скасаванне дыяву без перапісва

    Пашчатковыя падсказкі ў зв’язку з хаотычным кодам — це ўсё-такі повна перабудова. Тыпавы рэзультат чырвох месцаў пазней є предскладным:

    • стары прыкладнік яшчэ працуе ў рэальных умовах
    • замена адбылася толькі часткова
    • тепер трэба падтрымкі двум системам
    • команда выкараналася
    • работа над продуктам застаяла

    Поступовыя архітектурныя практыкі паўтароўна даўаюць лепыя рэзультаты. Яны пачынаюцца з выявлення рэальных проблем.

    Выявіце дорогія аспекты

    Стараюцца не падаваць у спрагу перерабіць усё. У замене выявіце тыя часткі продукту, дзе разліковікі сталквуюцца з труднасцямі пастойна, напрыклад:

    Checkout
    User Management
    Search
    Dashboard
    Reporting
    

    Для кожнага кандыдата шукайце гэтыя сігналы:

    • Багі, якія продовжваюць з’являцца ў той самы роўні
    • Файлы, якія людзі бояцца рэдагаваць
    • Компаненты, якія з’являюцца практычна ў кожным pull request
    • Часткі продукту, якія новым працавнікам трэба дольш часу, каб ўсваяць
    • Функцыі, чыя змены паўтараюцца ў некалякуючых модулях

    Роўні, якія пастойна з’являюцца, — гэта вашы «гарячыя точкі» заборгавання.

    З’явіце данні пра косты змены сёгодні

    Не трэба складных інструментоў. Дастатнька фіксацыя калькі показначыкаў:

    Average time to implement feature
    Average PR size
    Number of files touched
    Regression frequency
    Build time
    Test execution time
    Deployment frequency
    

    Связыванне боргу з такімаінжынярскімі рэзультатамі значна спрыяе выбору прыоритэтаў, адтуды ўмогацьвае паказаць, што адпаведны роздзял спамчвае выкананне, замест таго каб аргументаваць, што яго код некалякі.

    З’явіце правіла залежнасцей пры выборе шаблона

    Перш чым обсудзіць Clean Architecture або Feature-Sliced Design, спачатку раз’ясніце болей просты вопыт: чаму можа залежаць ад чаго? Простыя напрамкі шаравай структуры можу выглядаць так:

    app
     ↓
    features
     ↓
    shared
    

    У межах гэтага функцыя можа залежаць ад спакульнаваных будуючых блакоў:

    features/product
          ↓
    shared/ui
    shared/lib
    

    але спакульны код ніколі не павінен вяртацца ў функцыю:

    shared/ui
          X
    features/product
    

    Спецыфічная архітектура можа змяніцца з часам; саме направлення залежнасцяў палягае ў тым, што робіць яе цэлесообразной. Калі спяльны UI пачынае імпортуваць элементы з функцый настолькі продукту, ён больш не ўважаецца спяльным, і кожны корыстувач таямніча залежыць ад гэтай функцыйнасці. Гэтыя правілы таксама лёгка адмахнуць автаматычна за дапамою правіл лінтаў для шляхаў імпорту, чыяе застосаванне ператварае архітектурны намер у тое, што системы CI пераглядаюць у кожным запрошэнні да падключэння.

    Лейаут, оріѐнтаваны на функцыйнасці, для React і Next.js

    Няма ўніверсальнай структуры, якая падходзіць для кожнага проекту, але арганізацыя па функцыйнасцях стварае природныя межы:

    src/
      app/
        routes/
        providers/
        layouts/
    
    
    features/
        checkout/
          components/
          hooks/
          services/
          state/
          types/
        products/
          components/
          hooks/
          services/
          state/
          types/
        users/
          components/
          hooks/
          services/
          state/
          types/
      shared/
        ui/
        hooks/
        lib/
        types/
    

    Копіюванне эўхо імен папак не ёсць галоўной мэтай. Глыбокае значэнне мае тое, што прыналежнасць становіцца ясной: app спаўяе маршруты, падаёчыкі і лэйауты разам, кожная функцыйнасць володзіць своімі компонентамі, хукамі, службамі, станом і типамі, а shared мае толькі тое, што ў працоўнай меры є гэнерычным. Для паўтароўкі альтэрнатываў, адзірніце практычную паўтароўку шаблонаў структуры папак у React.

    Звычкі, якіх трэба утрымлівацца ў большым фронтэндзе

    Калі працуеце над большым кодбэсам на React або яго падтрымліваете, будзьце ажырнелівы да наступнага:

    • Глобалізацыя стану за замовчаннем. Большасць значэнняў ніколі не трэба выходзіць за межы свайго компонента.
    • Занадта ранняя абстракцыя. Спачатку разумейце варіяцыі, а тады створюйце абстракцыі.
  • Дадзенне эфекта як рэфлексу. Разважыце пра трык дадзеных перш чым сінхронізаваць што-небудзь.
  • Ачыць іншы файл utils.ts. Знайдзіце домэн, які павінен адказваць за логіку.
  • Пазначэнне цэлых сторанак як коду кліента. У App Router гэта ўсё болей каштовна.
  • Інсталяванне пакета для таго, што вже робіць платформа або фрэймворк. Кожны такі пакет стае давялівам для довгастаг адтрымання.
  • Рефактораванне перш чым ведаць рэзультаты. Адчыстка коду — гэта не бізнес-рэзультат; нижыя косты змян — так.
  • Ад напісання кращага коду да стварэння систем, якія можна зменяць

    Самая значныя змена, якая наступае з адпытам, — цэлкам іншая мета, а не новыя тэхнікі. Спачатку нейкразлічнае пытанне — як напісаць даны фрагмент коду краща. Пазней пытанне стае такім: як заліць систему простейшай для таго, хто будзе яе зменяць далей. Такая перафармацыя зменяе спосаб прыбліжэння да компонентаў і абстракцый, кантролю стану за дапамою Redux, TypeScript, Next.js, стайлінгу, тэстаў, загальнай архітектуры і залежнасцяў. Уместо таго, каб выгладзіць адзінаковыя файлы, вы пачынаеце оптымізаваць здатнасць системы развивацца.

    Заборгаванне — гэта інструмент, а не вор

    Немагчыма пазбітцяць тэхнічны гора, і спробы цього — глытак. Кожны продукт вынужданы робіць компрэсіі, і багато з яых ствараюць гору па сваім проектаванні: шырокая даступнасць, падтрымка калькольных бізнес-тэрэфаў адразу, падтрымка старых прыгледачаў, змена напрамку продукту чы адхватка прыобретанага прыкладнага програмнага забезпечэння. Нават выгоднае архітектурнае рашэнне можа стаць горай, калі змянюецца апошняя система.

    Таму «нульовая гора» — глытакі цэль. Рэалістычная мета — гора, якую каманда розумее, трывожыць пад контролем і можа пазбітцяць, калі яна начынае вплываць на выданне продукту.

    Што на самай працэ прадаеяе велікі прыкладны програма

    Варта быць точным ў вяскапных прычынах, таму што яны рэдкая разлічваюцца з тым, за што люди карагуць:

    • Проблемай часто не ёсць самыя складовы; проблемай являюцца адносіны межа ўсімі імі, якія ніхто не можа разумець.
  • Няма проблемы ў самам Redux; проблема ў тым, што ніхто не ведае, што самэўсёлкі ў яму патрабуецца.
  • Няма проблемы ў великай колькасці типаў TypeScript; проблема ў тых типах, якія больш не прадставляюць значычных меж.
  • Няма проблемы ў тым, што Next.js аддае можлівасць выкарыстоўваць серверныя і кліентскія компоненты; проблема ў командзе, якая не ведае, дзе самэўсёлкі межы.
  • Няма проблемы ў старым кодзе; проблема ў старых рашэннях, якія больш нельга безпечна зменіць.
  • Корача кажучы, тэхнічны борг — это не код, які выглядае некрасіва. Цэто код, які робіць змены непатрэбна дорогімі, а najleшая архітектура фронтэнду — это не тая, якая выглядае ідеальна з першага дня, а тая, якая все ўсё равна дазволяе командзе быстра працаваць праз пяць гадоў.

    Спіс пераконтрацых праз прыемліванне функцый

    Перш чым дадаць новую функцыйную можлівасць у кодбазу React, разглядзіце наступныя пытанні:

    • Чы є рэальная прычына, чаму гэты стан павінен быць глобальным?
    • Чыі серверная частка ўладае гэтымі дадзеннямі, чыі яно проста ў стане UI?
    • Чыі компонент берае на сябе другую чы трэцю адпаведальнасць?
    • Чыі новая абстракцыя рашае якуюсь проблему, чыі яна проста выглядае адначасовей?
    • Калькі функцыя ёсць власнікам гэтага бізнес-правіла?
    • Чыі гэты код могаў бы запрацаваць на серверы Next.js у замене?
    • Чыі тут сапраўды неабходны Effect?
    • Чыі гэта дадае ўтроху пакетаў, якія трэба будзе адтрымваць?
    • Чыі компонент дзейсна викорыстоўваецца ў розных функцыях?
    • Чыі новачак могаў бы разумець гэты код без запытанняў да таго, хто яго напісаў?

    Гэтыя запытанні зазвычай маюць большую цэннасць, чым ўжо яшчэ адны спіс наяўных стандартаў React.

    Тэмы, якія варта розглянуць далей

    Калькі пунктавых разговораў з гэтага кялікуляра заслугуюць на болей глыбокы аналіз самастоятельна:

    • Калі Redux насправды ўзьмецца за правы выбар у параваненні з локальным станом, Context, Redux Toolkit, RTK Query, спецыяльным кераваннем станам сервера, станам URL і станам форм.
    • Рефактораванне компонента з 1 000 ліній без парадоксалых наследкаванняў у прыменэнні як паслядовы кейс-стады.
    • Чы рэусаблівыя компоненты пагаршуюць працэс стварэння дапыткі, з увагай да проблемы «эксплозіі пропаў», композіцыі і систем дизайна.
    • Компоненты сервера проты компонентаў кліента ў Next.js, сфокусаваныя на рашэннях па граніцы функцыональнасці, а не на тым, што робіць "use client".
    • TypeScript у масштабных проектах, укладаючы any, гэнерікі, DTO-ы, типы домэны і API, а таксама проблемы дуплікацыі та інферэнцыі.
    • Што можа заменіць папку utils як месца для логіки.
  • З’ясаванне момента, калі програме патрэбна перабудова архітектуры, ідеяльна ў варыянце з модэллю абкладання, створанай на такіх крэтэрыях:
  • Change cost
    Bug frequency
    Dependency coupling
    Component size
    State complexity
    Build time
    Testability
    Developer onboarding
    

    Ключовыя выводы

    • „Борг“ фронтэнду — це сума маленькіх, разумных рашэнняў; старацца за тэндэнцыямі, а не за адзінамі змянамі.
    • Оцэнюйце стан кодавой базы па вартасці і рызыку змян, а не па тым, чы работае програма зараз.
    • За замовчаннем трэба заставляць стан на месца, спрыяць кэшаванню дадзенняў сервера, а глобалізаваць толькі тое, што справды ўніверсальнае.
    • Вядомей выбіраць межы і напрамак залежнасцяў, чым назвы папак або конкрэтныя методалогіі.
    • Поступова скасаваць „борг“ у выявленых проблемных месцах, а не рызыкуваць перапісвам.