Дзяржаўне адліквенаванне экранаў у React для станоў завантажэння, порожнечы, адказа пра апярэдзенне і павторнай спробы
Выучыце, як ствараць модэлі для кожнага стану, які можа маты экран React і форма — ад каркасоў і перзапытак да порожніх рэзультаатаў, корисных памылак і захавання ад двойнага надсылкі.
Экран, який ідеальна працюе з локальной базайнамі дадзеных, можа перестаць працаваць у рэальных умовах, калі корыстувачы сталкуюцца з повольным інтэрнетам, порожнімі табліцамі і нефункцыональнымі канцэнтрамі. У гідзе показаны всі стадіі, якія пераходзіць экран на базе React і його формы, а таксама даўаеся чарт для прадзействовання кожнага з іх пры стварэнні, перш чым пачаць пісаць код на JSX.
Ментальная модель большасці нестойкіх экранаў
Калі спачатку прадумваецца структура компонента, ўсё здаецца простым: запрашаецца даны, а потым яны атрымліваюцца і відображаюцца.
Fetch data → Show data
У такой модэлі няма месца для парадакоў. Экран у рэальных умовах мае разныя варыянты роботы: ён пачынае завантажэнне, можа даць результат успеху або порожній, можа выклікваць памылку, і для вярнення да нормальнага стану ёму патрабуецца перапрыбутак або паўторны запрашэнне.
Loading → Success → Empty
↓
Error
↓
Retry / Refetch
Спрыяйце завантажэнню як справжнему стану зі сваёй сабой дызайном
„Паказаць спінер“ здаецца цэлкавай адказам, так сама як і „загорнуць у try/catch“: гэта не няўерна, але рэдкасці калі гэта пакрывае тое, што перажываюць корыстувальнікі. Две запытанні паказваюць гэты прасёк. Как выглядае экран, калі адпаведзь приходзіць за чатыры секунды? І што выходзіць, калі ён прыходзіць за 40 мілісекунд: чы розбірае корыстувальнік, што спінер меркаеся праўда адно кадру, і спрыяе таму, што інтерфейс здаецца нестабільным?
Пачатковая загрузка проты паўтарной загрузкі
Дапамагае раздзеліць процес загрузкі на два аблакоўныя станы:
- Пачатковая загрузка. На экране яшчо нічога няма, таму скелет, які адпавядае гэтаму фінальнаму лейауту, ёсць разумным і нават прыемным выборам.
- Паўтарная загрузка. Даныя вже видны, напрыклад пасля таго, як корыстувальнік нажме кнопку „Апавярзіць“ або змені фільтр. Їх вычышэнне і замена на загрузчык усіяго экрана пазбавляе нас кориснага контексту.
Падчас павтаральнага запрашэння трэба заставіць раней выкарыстоўваныя данні заставацца на экране і паказаць маленькі індыкатор пры самай элементе, які апдэюўаецца. Індыкаторы запуску обробкі не даюць нікому паверы; у найкращым случае іх толькі тлумачаюць. Для дужа шыракіх адпаведзей розгледзіце можлівасць на час адкласіць паказанне індыкатора, каб ён з’яўляўся толькі тады, калі чаканне стае пометным.
Чаму функцыональны запрашэння ў формате fetch — толькі частка задачы
Сама просьба ёсць простай часткай:
const res = await fetch("/api/students");
const data = await res.json();
setStudents(data);
У гэтым фрагменте не адпаведзена на запыткі ўсё, што яго абрамляе. Чаго бачыць корыстнік, пакуль просьба ў стане очаквання? Што, якфункцыя fetch будзе адхілена, альбо сервер адпавясі статусам, не ў межах 2xx (што fetch сама па сабе не вважае адказам пра бяду)? Што, як тэла запрашэння будзе порожнім масавым элементам? Корыстны вопыт — не тое, як запрашаць данні, а тое, у яких станах гэта запрашэнне можа знаходзіцца ў будь-якы момент.
Якщо students — ўсё, што ёсць у стане, то рэшта інтэрфейса павінна выважваць ситуацыю на адной падставе таго, чы гэтыя зменныя ў значэнні null, порожнія чы заполненыя. Значэння null можа значыць „всё яшчэ завантажуецца“ або „запрос не выйшаў“, і няма спосабу ўжо развідрóżніць іх. Гэта проста здагадкі, перакрытыя маскай становага керавання.
Порожній рэзультат — гэта успех, а не адказка
Этот момент варта выделіць, таму што багато кодавых баз па-субтыльнаму робяць тут помилку. Адказка 200 OK з [] означае, што сервер правільна выканаў свою задачу: ён паведамляе, што няма нічога, што можна было б паказаць.
„Няма знайдзеных учняў“ і „Не удалося завантажыць учняў“ апісваюць абсалютна разныя ситуацыі, пры тым багато дапаможных прыладоў для абох выканаюць той самы сірый заместак. Адна і тая ж структура <p>Осё пайшло не так</p> для абох ситуацыяў спалюе два разныя становішчы, якія трэба ліквідаваць адначасна: порожні стан можа практычна запрасіць корыстніка выключыць фільтрацыю, тады калі стан адказкі трэба поясніць прычыну неудачы і запропанаваць спосаб ўсунуць яе.
Як выглядае корыстны стан адказкі
Універсальная фраза „Осё пайшло не так“ нікому не дапамагае. Кращы стан адказкі:
- паведамляе корыстніку тое, што ён можа зробіць, напрыклад, пераканаліцца ў налажанасці з’ѐеднання або спробаваць праз час;
- прадстаўляе кнопку павторнай спробы, калі є рэальныя шансы на успех;
- ніколі не прадстаўляе первасныя даны пра адказку або внутраніе пакеты з помилкамі ў інтерфейсе;
- заставаецца толькі ў частцы экрана, дзе выйшла проблема.
Пяты элемент являецца тым, які частаўная праходзіць без уваги. На панелі керування з пяцьма віджэтамі адна неудачная запытка не павинна змацкваты чатыро, якія завантажыліся з успехам. Карэнтаванне адзначэнняя пакрыцці па разделам, через локальны стан або межы адзначэнняя пакрыцці навкола незалежных рэгіонаў.
Формы праходзяць свой сопны цикл
Запіс дадзеных мае ўсё больш станоў, чым яго чытанне:
Idle → Editing → Submitting → Success / Error
Захаванне ад падвойных запускаў
Разглянем пользователя, які трыць разоў натыкаецца на кнопку «Адправіць», таму што пасля першых двух натысканняў кнопка не давала жадных візуальных парадактов. Якщо толькі кнопка не ўвыключана пад час адправкі, або працоўнік не пераканальваеся за дапамогою флага isSubmitting і не вяртаецца раней, калі запит вже ў процесе обработкі, сервер прыме тры запиты. Для формы замовлення гэта значыць, што адзін кліент заплатіў тры разы за адну футболку. У випадку платежаў бэкенд таксама должен усунуць дуплікацыі, напрыклад, за дапамогою ключа ідэмпотентнасці.
Зберагчы вхідныя даны пользователя, калі адправка не выйшла
Калі запит не выйшаў, залишыце форму заполненай. Змусіць каго-небудзь перапісваць дугі тэкст пасля проблемы з сетю — це марнатратства ўжо выдзялёванага часу. Паказы пра бракы падтверджэння, чы гэта правіла з боку кліента чы сервера, должны выкліквачыцца пры самым поле, да якога вони стосуюцца, а не ў расплывчатай панелі вышэй на форме.
Заменіце розрозненыя логічныя значэння на іменаваныя статусы
Якщо не ставіць галочку, компанент часта накаплівае такі флагі:
isLoading, isError, isSubmitting, hasData, isEmpty
Кожны флаг сам па сабе мае сэнс. Разам пяць логічных значэнняў дазваляюць стварыць 32 комбінацыі, з якіх толькі калькі маюць значэнне, і нічога не заважае таму, каб isLoading && isError былі правдзівыя або ўжо.
Старонка зі спісам студэнтав не патрэбуе формальной бібліятэкі для керавання станамі. Яе патрэбна толькі свядомая рашынка, якая прыймаецца ўжо да першага вызову useState, пра тое, якія конкрэтныя станы можа займаць экран. Тэкучы стан трэба представіць адной значэннем, напрыклад, палаткай status, якая храніць "loading", "success", "empty" або "error", і нехай рэндарынг залежыць ад гэтага значэння, а не ад пераскладання ситуацыі з кальколька незалежных флагоў. У TypeScript дискримінаваная юнія не дазволяе представляць немагчымыя комбінацыі; паст па тэме typing useState, useReducer і спецыяльныя хукі дэтальней раскрывае аспекты типавання.
Спіс пераконтрацоў, якія трэба адрабаты пры стварэнні наступнага экрана
Адказаць на гэтыя запытанні пры напісанні return компонента:
- Што ўзірмаеся на вокзе раней, чым прыйдуць данні?
- Што адбываецца, калі запыт не выйшоў?
- Што адбываецца, калі запыт выйшоў, але нічога не вернуў?
- Што бачыць корыстнік пад час апшурання існуючых данаў?
- Што адбываецца пад час выканання формы?
- Што адбываецца, якщо форма будзе выканана два разы?
- Што адбываецца, калі перакананне не выйшло?
Заключэнне
Разлік межа дэмаверсіі і продакшн-інтэрфейса рэдка крыўцяцца ў «гарным» сцэнарыі. Ён крыўцяцца ў станах загрузкі, порожнечы, адказу пра памылку, апшурання і выканання, якія з’яўляюцца ў момент участі рэальных корыстнікаў і рэальных сетей. Акрамячы гэтыя станы чыста, спецыяльна праектаваць кожны з іх, трэба локалізаваць памылкі і захаўваць вхідныя данні корыстніка і рэзультаты работы сервера ад павторных чы правальных выкананняў.