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

Дзеянне-скалярованы дизайн для React: шары, правілы імпорту і калі паставіць канец

Дазвольце дазнацься, як шары падходу Feature-Sliced Design, правілы однонаправленага імпорту, простыя публічныя API і @x крос-імпорты дапамагаюць расплутаць кодавесы React, і калі варта адпраўляць толькі частку гэтага падходу.

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

Крыніцы: Feature-Sliced Design in React: How to Structure a Scalable Codebase (and When It’s Overkill)

Практычна кожны кодбаза на React, яка прыжывае праз другі год за свою існаванне, мае папку utils/, якую ніхто не хоча ачыць. У ёй знаходзіцца форматар дат, кэш для павторных спроб, частка машыны станоў і файл helpers.ts, які імпортуе матэрыял з трох папак функцый, а две з тых папакі імпортуюць яго знову, таму ніхто не можа сказаць, на шта саме залежыць канкрэтны элемент. Методологія Design-Sliced (FSD) спецыяльна створаная для рашэння гэтай проблемы. Пасля чытання гэтага тексту вы знаеце пра ўсе шасць яе слоёў, аб адной правілы імпорту, якая виконвае большую частку работ, пра тое, як практычна размежаваць часткі коду для можлівасці безпечнага рефакторавання, і, гэта найважлівейша частка, пра тое, як адначыцца, чы рэальна ўваходжэнне ў практыку FSD для вашай аплікацыі, чы не лепш выбраць менш масіўны ўскладнены варыянт.

Чаму папкі, заснованыя на типах, не дазволяюць росту

Класычныя структуры групаваюць файлы па таму, чым яны ўсё (компаненты, хукі, інструменты), а не па таму, для чаго яны выкорыстоўваюцца. Гэта падходзіць для маленькага прыемніка, але калі з’яўляюцца рэальныя бізнес-домены, структура пачынае выглядаць так:

src/
 components/ # 200 files, no domain grouping
 hooks/ # useAuth next to useCartTotals
 utils/ # the grab-bag
 services/ # api calls that import from utils, which imports back

У такай структуре выклікаюцца два проблемы. Першая — запитанне „Дзе патрабуецца гэты файл?“ не мае чыстай адпаведнай адказу, таму все неяснае потрапляе ў папку utils/. Другая — няма нічога, што б обмежыла напрамак залежнасцяў, што дазволяе стварацца цыклам. Уявіце сабе, калі сервіс з обробкі замовлення запрашае файл utils/user.ts, які, у свою чаргу, залежыць ад services/auth.ts, а сервіс аутэнтыкацыі зноў імпортуе сам сервіс з обробкі замовлення. Такія цыклы застаюцца непазначальнымі. Як правило, яны проявляюцца значна пазней у вигляде значэння undefined, калі модуль пачынае працаваць, таму што адны з файлаў у цыкле быў обрабоўаны раней, чым завершылася обробка файлу, на які ён залежыць, і тады структура залежнасцяў стае настолькі заплутанай, што ёё важка прачытаць.

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

Шэсць слоёў і што знаходзіцца ў кожным

FSD арганізуе код у слоі за фіксаваным порядкам, зверху да ніжней часткі: App, Pages, Widgets, Features, Entities, Shared. Таксама існуе старэйшы слой Processes, але ён заняты і можа быць ігнораваны. Порядак не ўсё толькі дэзартацыйны; ён вялічы, хто можа залежаць ад каго.

  • App: маршрутызацыя, точкі входу, глобальныя стылі і прадстаўнікі.
  • Pages: цэлыя сторанкі або большыя часткі інтэрфейсу, прычаленыя да певнага маршруту.
  • Widgets: большыя, самостойныя блакі інтэрфейсу, якіе самыя сабе выпалняюць цэлую функцыю.
  • Функцыяны: рэалізацыі функцыйнальнасці продукту, якія викорыстоўваюцца ў калькі месцах.
  • Энтыты: бізнес-канцэпцыі, з якімі працуе проект, такія як корыстнік або продукт.
  • Спадзяльны код: код, які можна викорыстоўваць знову без патрэбы ў бізнес-знаёмствах, укладаючы ў сябе систему дизайна і загальныя памагчыкі.
  • Чатыры сярэднія шары (Pages, Entities і іншыя) дзелюцца на сектары; кожны сектар адпавядае аднаму бізнес-домэну, напрыклад entities/user або features/auth. Шары App і Shared не маюць сектароў. Кожны сектар далей дзеліцца на сегменты па тэхнічным значэнню: ui — для компанентаў, api — для запытоў і типаў адпаведзей бэкенда, model — для схем і хранэння дадзеных, lib — для памагчыкаў, якія викорыстоўваюцца толькі ўнутрь таго сектара, і config.

    Правілла імпорту, які не дозволяюць цыклам

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

    Застосавіце гэтае правілла да цыклу, пра які гаворылася раней. Пасля переразбівання логіка параду знаходзіцца ў entities/order, логіка корыстувальніка — у entities/user, а процес аутэнтыкацыі — у features/auth. Дазволеныя залежнасці становяць:

    features/auth → can import entities, shared
    entities/order → can import shared only
    entities/user → can import shared only
    

    Якщо entities/order прагне імпортуваць даны з features/auth, гэта ўскладненне структуры архітектуры, а не проста пытка паследавання стылю. Стварыцца цікл немагчыма, адказь кожная грань у графе залежнасцей ведае адносна ніжніх рэгіонаў. Гэта таксама зменяе спосаб разумавання коду: уместо таго, каб аналізаваць імпорты па файлам, трэба думаць пра шасць шароў, якія людзі можуць запамятаваць.

    Публічныя API захоўваюць внутрашняе зместа сэкцый у таёмнасці

    Кожная сэкцыя выклікае публічны API, зазвычай index.ts, які перывыкланае тое, што можуць выкарыстоўваць стороннія корыстувальнікі. У дакументацыі FSD чыста адзначана мета: выклікаць толькі тое, што неабходна, і захоўваць деталі рэалізацыі у таёмнасці.

    Дужа распашчастая версія такога файла паспела знецініць гэту мету:

    // features/auth/index.ts (the anti-pattern)
    export * from "./ui";
    export * from "./model";
    

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

    Альтернатыва — явна перыякаваць кожны экспорт:

    // features/auth/index.ts (the boundary)
    export { LoginForm } from "./ui/LoginForm";
    export { useSession } from "./model/useSession";
    // tokenStore stays internal. Nobody outside auth can touch it.
    

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

    У даследжэннях FSD ёсць адна прытаманна проблема: файлы index можу ствараць цыклічныя імпорты, спрычыняць проблемы з адсортаванням коду ў падрагах shared/ui і shared/lib, а таксама споваліць процес зборкі ў большых праектах. Практычны спосаб выйсця — заставіць публічныя API быць чыста выражанымі і мінімальнымі, а таксама быць вышэйша адзярожным у падраге Shared, дзе багато маленькіх, незалежных модуляў краща імпортуваць безпосередна, чым перадаваць іх через аднаго вялікага модуля.

    Межавы імпорты, чрэзмерна фрагментацыя і дазвол на перакананне лінтара

    Значэнне @x для аб’ектаў, якія адна да другойя паводзяцца

    Дзеянняякія правамерна залежаць адзінадзінаго; напрыклад, замовленне патрабуе тип User. Услягаючы таму, што імпорты з таго ж слоя забараненыя, версія v2.1 стандартазавала абазатку @x самэй для такіх ситуацыяў. Слайс user публікуе спецыяльную точку входу, прызначаную для слайса order, а order імпортуе з яго:

    // entities/user/@x/order.ts
    export type { User } from "../model/types";
    
    // in entities/order/model/types.ts
    import type { User } from "entities/user/@x/order";
    

    Назва файла чытна паказвае адносіну і дазволяе яе шуканне: entities/user/@x/order.ts — гэта частка user, якую order можа бачыць. У дасведчэннях гэта адзіначаецца як спецыяльны выключэнне. Старацца зменшыць колькасць міжслойных імпортаў і вжываць @x толькі ў слое Entities. Якщо вам патрабуецца яго ў всім кодбэсе, гэта знак таго, што вашы слайсы створеныя не на правых месцах.

    Флагі Steiger паказваюць як наявнасць адхыленняў, так і занадта сложную структуру

    Правіла дапамагаюць толькі тады, калі іх выкананае. Steiger – апаратна перапрацоўка для FSD (яе правілы поставляюцца як @feature-sliced/steiger-plugin), працюе без налаштаванняў і на момент напісання тэкста заставалася ў бета-версіи. Кроме перагляду правілаў імпорту, ён таксама запобегае занадто складной структуры: правіло insignificant-slice актываецца, калі функцыя аб’екта викорыстоўваецца толькі на адной сторонцы, а правіло excessive-slicing паведамляе, калі проект разбіўся на занадта многа маленькіх частак. Калі Steiger адзвярнуў увагу на незначную частку, прыйміце гэты савет: вы створылі папку, для якой бы простая локальная функцыя была достатня.

    Калі цэлы FSD – гэта занадто

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

    У спецыфікацыі гэта узнавама, таму версія v2.1 рэкамендуе падход "спачатку сторынкі" для адпрацоўкі. Пачніце толькі з App, Pages, Widgets і Shared, і падумайце пра тым, чаму не застацца на гэтым. Для багато прыстаўакаў гэта сама по сабе ёсць завершаная і зрозумелая структура.

    Перакладаць код у роздзелы Features і Entities трэба толькі тады, калі ёсць конкрэтная прычына. Корыстным падказкам є чакаць, пакуль тая ж логіка будзе неабяжна для троха або больш за трохі стораначакоў чы віджэтав, перш чым ствараць для яе окремы роздзел. Даколі гэта не адбудзецца, правым выборам будзе процес прайшчыпання, які застаёцца выключна ўнутрь pages/checkout, а не якісь штучныя способы. Однаковая перадпісваўка можа быць простаю збыракою; трохі таких прыкладоў вказывае на справжню законамірнасць, і самэ гэта є метай існавання шара Features.

    Якщо вы таксама разважаеце варыянт FSD проты простэйшых структураў на адной папцы з функцыямі чы структураў, базаваных на домэнах, наша парэльная перапісваўка шаблонаў структуры папак у React паказвае всі альтернатывы.

    Короткі чартак для адпрацоўкі

    • Працюйце з FSD на абсалютна гэта можлівасць, калі команда складаецца з больш за калькі інжынероў, колькасць бізнес-домэнаў продовжвае раставаць, а папка utils/ уже ператворылася на месца скарыдкі.
    • Для маленькага продукту з адним чыстым власнікам заставайце правило імпорту назад і стварайце простыя публічныя API, адмовіўшыся ад дапаможных слоёў.
    • Кожны файл index.ts пішыце як чысты список экспортаў; утримайцеся ад использовання export *.
    • Заставайце @x толькі для апыленаў межаў між ентытамі, а частае ўжыванне такога сімболу спрыймайце як проблему межы.
    • Застосавіце інструменты для перагляду коду, такія як Steiger, каб правілы і паведамленні пра непатрэбныя часткі коду аплываліся автаматычна.

    Заключэнне

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