Галоўная / Артыкулы / Дыягназаванне проблемаў з выконанням React за межамі часу адпаведзьбы API

Дыягназаванне проблемаў з выконанням React за межамі часу адпаведзьбы API

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

1764 слоў

Скорасць і ясна структура ў проектах на React часта зламваюцца з той самы падстаўны прычынай: тое, што ззовні выглядае завершаным, на самай справе застаецца незавершаным. Бэкенд, який адпавядае за 200 мілісекунд, нічога не гаворыць пра тое, што браузер усё ўсё仍 должны зробіць, прычаму корыстнік не можа нічога побачыць чы робіць з тым. Так сама проект, у яком усе компоненты працуюць правільна, можа заставляць вялікі напрэнергію пад час роботы, якщо супаўзвязаныя файлы розбросаны па всім кодбэсе. Обая проблемы маюць аднаковы урок: саме тое, што выходзіць пасля завершэння очвідной часткі — пасля таго, як API адпавядае, пасля стварэння функцыі — вялічыць, чы прыкладнасць дзейсна здаецца швайной і прыемнай для адтрымання.

Калі API не ўяўляе сабою вузькое месца

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

API Request
    ↓
    200ms
    ↓
Data received
    ↓
JavaScript processing
    ↓
React rendering
    ↓
Browser painting
    ↓
User sees the result

Этыя 200 мілісекунд на перадзеўжку та павратку — толькі вступна сцэна. API можа быстра завершыць свою роботу, тады калі браузер усё ўсё мае дужы список задач, якія трэба адрабатваць — атрыбутаванне компонентаў, апдэйт DOM, выкананне расчыткаў та адрасаванне пікселей. Якщо хоць адна з гэтых дзеянняў є неэфективной, корыстнік відчувае повалку, якая не мае нічынага з серверам.

Компоненты, якія атрыбутуюць больш, чым трэба

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

function Dashboard() {
  const [count, setCount] = useState(0);
return (
    <>
      <button onClick={() => setCount(count + 1)}>
        {count}
      </button>
      <LargeComponent />
    </>
  );
}

Якщо LargeComponent мае сотні элементаў або выконвае складныя вычысленні, кожна клікання можа змусіць React перадзеясць набагато большую кантэнт, чым насправді трэба для данай інтеракцыі. Існуюць такіі інструменты, як React.memo, useMemo і useCallback, якія дапамагаюць утримацься ад такой зайвай працы, але іх не трэба застаўляць у кожнай ситуацыі. Кращы падход — спачатку выявіць, якае рэндарыўанне є насправды дорогім, а потым оптымацаваць самэ гэтае разлічнае каскад, а не проста пакрыць весь кодбэйс мемоізацыяю з прыводу прыzwычкі.

Дзялёныя спісы все ўсё заставляюць ствараць дзялёныя DOM-дрэвы

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

{users.map(user => (
  <UserCard key={user.id} user={user} />
))}

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

Косцы, спрывучаныя ў вашым пакете JavaScript

Корыстнікі не відчуваюць часу адпаведзення вашай API безпосередна — яны відчуваюць, сколькі часу прайшло, прычымай кантэнт сторанкі стаў можна выкарыстоваць. Якщо браузер спачатку павінен завантажыць і выконаць калькі мегабайтаў JavaScript, тады адпаведзення за 200 мілісекундах захаваецца пад набагато медленейшым лянцюгам: завантажэнне, парсаванне, компіляванне, выконанне і нарэшце адразаванне. Усё гэта павінна выйсці раней, чым стане можліва якая-небудзь взаімадзейнасць.

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

const Settings = lazy(() => import("./Settings"));

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

Дорогія операцыі, якія выконваюцца пасля адпаведзі

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

const filteredUsers = users
  .filter(...)
  .sort(...)
  .map(...);

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

Пошук рэальнага вузькага месца замест адгадвання

Едынаві надзеяны спосаб з’ясаваць, яка з эйных проблем насправды стоіць за сповольненням, — гэта мераванне, а не прыпускі. Chrome DevTools і React DevTools разам можу паказаць, дзе самэўсёліва выкорыстоўваны час:

  • Вікно «Сеть»: насколькі шыбкаа на самай працоўвае API?
  • Вікно «Прыдатнасць»: дзе браузер выкорыстоўвае свой час пасля прыбыцья адпаведзення?
  • React DevTools: якія компаненты перерэндаруюцца і як часта?
  • Lighthouse: што самэўсёліва пагражае канцэпцыі вядомасці корыстніка?
  • Аналізатор пакетаў: сколькі JavaScript на самай працоўвае ў браузеры?

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

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

У організацыі таксама є такія ж скрытые затраты

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

Проблемы з групаваннем па типе файлу

Багатыя прыемкі пачынаюцца з структуры, якая раздзеляе файлы па тым, каму яны належаць:

src/
├── components/
├── hooks/
├── pages/
├── services/
├── utils/
└── types/

Спачатку всё выглядае адмініструема. Але калі проект расте, кожны з гэтых папак наполняецца сотнямі несувязаных файлаў. Падазроумцем, якія вам патрабуецца зробіць змены ў функцыях профіля пользователя — вам можа знадобіцца перайсці між components/, hooks/, services/, types/ і utils/, каб толькі змяніць усе, што зв’язанае з гэтай функцыяй. Усё, што прыналежыць да гэтай функцыяй, распадаецца па всім проекту. Тэхнічна ён все ўсё працюе, але чым больш він расте, тым менше стае інтуўітывным.

Групаванне па функцыях у замену на групаванне па типах

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

src/
└── features/
    ├── auth/
    │   ├── api/
    │   ├── components/
    │   ├── hooks/
    │   ├── types/
    │   └── index.ts
    │
    ├── profile/
    │   ├── api/
    │   ├── components/
    │   ├── hooks/
    │   ├── types/
    │   └── index.ts
    │
    └── dashboard/

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

Чаму ў практыцы гэта здаецца болей ясным

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

Развіце прыладу без падвышэння хаосу

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

features/
└── notifications/
    ├── api/
    ├── components/
    ├── hooks/
    ├── types/
    └── index.ts

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

Кращая саўместная праця ў команде

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

База коду, якая сама сабе пояснюецца

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

Калі групаванне файлаў па типе ёшчэ мае сенс

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

Заключныя меркі

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

Супаўзеўныя матэрыялы

  • Розумеўць JavaScript Proxy: пасткі, Reflect і рэакtyвныя шаблоны — Дазвольце вам дазнацца, як об’екты Proxy і Reflect у JavaScript перехопляюць доступ да атрыбутаў, што дапамагае рэалізаваць верыфікацыю, вяртабельныя атрыбуты і рэакtyвныя фреймворкі.