Стандарт розробки React для початківців
Усім привіт, мене звати Максим Маслов, і це моя перша стаття на DOU. Вона розрахована на усіх React-розробників рівня trainee/junior і стосується тих робочих випадків, коли немає сильної потреби у використанні state-менеджменту.
Структура проєкту
Для забезпечення організованості та структурованості кодової бази важливо дотримуватись загальної файлової структури та ієрархії застосунку.
Ознайомтесь з наступним скріном.
Приклади:
Папка assets може містити папки images, fonts, styles
Папка blocks може містити папки:
- MyProfile, яка буде містити в собі папки з блоками AboutMe, MyContacts і т.д.
- Settings, яка буде містити в собі папки з блоками ContactSetting, PaymentsSettings і т.д.
- common, яка буде містити в собі папки з загальними блоками Header, Footer, Sidebar і т.д., які використовуються в багатьох частинах застосунку.
і тому подібні блоки.
Блоки треба обовʼязково розділяти за папками, щоб було зрозуміло, який блок до якої сторінки належить.
Папка components може містити форми, а також компоненти, які складаються з ui (Input + Button), які надалі можуть бути теж перевикористані в якості єдиного компоненту.
І так далі. Треба обовʼязково зберігати ієрархію папок, щоб завжди було інтуїтивно зрозуміло, що до чого належить. Навіть якщо в папці blocks/MyProfile/AboutMe буде тільки 1 файл (AboutMe.jsx), все одно він повинен бути у своїй папці AboutMe.
Саме собою зрозуміло, що якщо в проєкті додатково зʼявляються якісь глобальні зміни, наприклад застосування state-менеджменту, то зʼявляється відповідна папка, наприклад src/redux.
Принцип єдиної відповідальності
Компоненти мають бути максимально простими та спеціалізованими. Замість створення одного великого компонента, розділіть його на менші частини зі своїми власними обов’язками та функціональністю. Це полегшить розуміння і тестування коду.
Наприклад, якщо у вас є складний компонент User, ви можете розділити його на UserInformation та UserActions, де перший відповідає за відображення інформації про користувача, а другий — за взаємодію з користувачем.
Приклад:
Кожний компонент має бути окремим файлом, а якщо в нього є власні стилі або окремі додаткові файли, які до нього відносяться, то для цього повинна бути створена окрема папка всередині основної папки компонента User.
Використовуйте кастомну обгортку для однакових блоків
Якщо у вас є однакові блоки або компоненти, варто розглянути можливість створення кастомної обгортки. Це дозволить вам забезпечити єдиний код, полегшить його утримання і пришвидшить подальшу розробку.
Приклад:
В цьому прикладі, попри те, що контент всередині цих блоків принципово відрізняється, кожен з цих блоків має однаковий колір фону та однаковий радіус. Якщо ви створюєте багатосторінковий сайт, буде дуже зручно створити кастомні обгортки для кожного блоку. Окрім кольору фону і радіусу кутів можуть бути додаткові стилі у вигляді тіні, градієнта, розмиття та внутрішніх відступів.
Не страшно, якщо у вашому проєкті використовується декілько різних обгорток. Для кожної варто створити свій компонент:
І надалі це можна використовувати таким чином:
До речі, помітили яка крива верстка у LinkedIn ?
Використовуйте map() для відображення однакових компонентів
Приклад, як не варто робити:
Чому цей підхід — погана звичка? В багатьох випадках ми не можемо знати завчасно скільки у нас буде елементів і який буде їхній зміст, тому що дуже часто такі дані приходять з бекенду.
І якщо в пет-проєкті, коли ви робите якесь меню, то так ще спрацює, а ось в реальному житті вам все одно доведеться використати map().
Приклад, як правильно:
Створюємо в окремому файлі масив, який повинен містити в собі дані елементів. Якщо це заглушка для тестування і надалі нам ці дані будуть приходити з бекенду, то можна створити в папці mock, а якщо це незмінні днні, то можна в папці definitions або в папці data.
Далі, відображаємо кожний елемент:
Також обʼєкти з інформацією про елементи можуть містити і функції, наприклад, які будуть спрацьовувати при кліці на елемент.
Зверніть увагу, метод map() першим параметром приймає елемент, а в цьому випадку ми його деструктурували і замість listItems.map((item) => ...) написали listItems.map(({id, text}) => ...), таким чином ми дістали зміст цього item.
Використовуйте кастомний хук замість імпорту контексту
Якщо вам потрібно передати дані або функції кільком компонентам, краще використовувати кастомний хук замість імпорту контексту. Це дозволяє зробити код зрозумілішим та пришвидшить розробку.
Зазвичай, для доступу до контексту, в провайдер якого обгорнута ділянка коду, до якої він належить, ми звертаємось наступним чином:
Замість цього можна імпортувати хук useContext з «react» безпосередньо в файл нашого контексту і в кінці цього файлу створити простий кастомний хук, який буде виглядати таким чином:
Завдяки цьому, в умовах налаштованого автоімпорту, ми дуже швидко можемо звертатись до нашого контексту таким чином:
Зверніть увагу, тут ми теж деструктурували обʼєкт, який нам повертає контекст, щоб одразу дістати ці значення, які нам потрібні.
Використовуйте локальний контекст для окремих багатофункціональних компонентів
Якщо у вас є компонент, який виконує багато функцій або має велику кількість обов’язків, розгляньте можливість використання локального контексту, навіть якщо ваш застосунок вже огорнутий глобальним контекстом. Глобальний контекст може бути корисним для передачі загальних даних або функцій на рівень застосунку, але використання його для кожного компонента призводить до зайвого ререндерингу.
Оскільки при оновленні будь-якого стану в глобальному контексті відбувається ререндеринг усіх компонентів, які використовують дані з цього контексту, варто розглянути винесення деяких станів в окремий локальний контекст. Це допоможе уникнути зайвого ререндерингу тих компонентів, які не залежать від цього стану.
У деяких випадках можна використовувати мемоїзацію локальних контекстів, щоб запобігти зайвому ререндерингу. Це передбачає кешування значень контексту і його використання лише при зміні необхідних даних. Однак, це більш просунута тема, яка потребує глибшого вивчення та розуміння. Ми до неї повернемося пізніше.
Важливо також зазначити, що при використанні глобального контексту необхідно зберігати якомога менше глобальних станів. Чим більше станів зберігається в глобальному контексті, тим більше компонентів будуть ререндеритись при їх зміні. Тому розсудливим підходом є обмеження глобальних станів до необхідного мінімуму.
Перевикористання функцій і кастомні хуки
Якщо якась функція може бути задіяна в двох або більше компонентах, варто її винести окремим файлом у відповідну папку: utils/helpers/function.js.
Якщо ця функція містить хуки, то вона називається кастомним (користувацьким) хуком. Її назва повинна починатись зі слова «use» і міститись вона повинна у відповідній паці: utils/hooks/useCustomHook.js
Багато кому не зрозуміло поняття кастомного хука. Ось приклад:
Своєю чергою цей хук може використовуватись в компонентах наступним чином:
Використовуйте defaultProps*
defaultProps — це стандартна властивість, яка використовується в React для встановлення значень за замовчуванням для пропсів, які очікує компонент, якщо вони не передані або мають значення undefined. Використовується ця властивість тільки в тому випадку і тільки для тих пропсів, без яких компонент не зможе правильно функціювати.
Загалом, використання defaultProps допомагає зробити компонент гнучкішим, надійнішим і зрозумілішим, спрощуючи розробку та підтримку коду.
Приклад:
В такому випадку, якщо у нас не прийде image в пропсах, то замість стандартної браузерної «зломаної іконки» буде відображено дефолтне зображення. Такий приклад ви можете побачити в кожній соціальній мережі. Це маленький приклад, і теоретично його можна замінити наступним кодом:
І хоча другий варіант, здається, має меншу кількість рядків, пріоритетним завданням є досягнути максимальної читабельності коду. Тому, безумовно, варіант з використанням defaultProps є набагато зрозумілішим. Крім того, в реальному житті бувають ситуації, коли компоненту потрібно визначити багато значень за замовчуванням, і використання другого підходу може призвести до спотворення коду.
*В деяких ситуаціях, наприклад, якщо ми використовуємо forwardRef, defaultProps може не спрацьовувати. В такому випадку можна задавати значення за замовчуванням при деструктуризації пропсів самому атрибуту:
const Avatar = ({ image = defaultImage }) => {}
Є думка, що такий підхід більш очевидний, але я раджу його використовувати тільки якщо неможливо використати defaultProps.
Використовуйте бібліотеку PropTypes
Якщо в проєкті ми не використовуємо TypeScript, то без додаткових зусиль і знань, ми можемо частково закрити питання валідації типів, а конкретно валідацію пропсів.
PropTypes дозволяє вказати очікуваний тип для кожної властивості компонента, такий як рядок, число, масив, об’єкт і т.д. Це допомагає уникнути передачі неправильних типів даних і непередбачуваної поведінки. А також дозволяє вказати, які властивості є обов’язковими для передачі до компонента.
Це забезпечує правильне використання компонента і уникнення помилок виклику. Вже на етапі розробки, завдяки помилці в консолі, ми будемо знати, що до нас в пропсах в конкретний компонент прийшов, наприклад, масив, але цей компонент очікує на обʼєкт.
Приклад:
В цьому випадку ми зазначили обовʼязкові типи даних, які ми очікуємо, а також те, що нам обовʼязково повинен прийти проп name.
Якщо ми передамо в пропси невірні типи даних, то нас про це одразу попередить бібліотека.
Ще один приклад:
І хоч прописувати в компоненті інлайн-стилі — це не дуже гарна практика, але суть повинна бути зрозуміла. Якщо у нас в дизайні можливі тільки три конкретних кольори для кнопки, то ми прописуємо їх перелік у відповідному масиві, а також зазначаємо, що колір обовʼязково повинен бути переданий.
Неочікуваний колір в цьому випадку викликає помилку.
Ще один приклад:
В цьому випадку ми прописали, що допустимі значення text — це або рядок або число. І можна подумати, ну хто буде передавати, наприклад, обʼєкт в такому випадку? Я навів просто максимально зрозумілий приклад. В реальних проєктах з сотнями компонентів, і коли пропси можуть буди результатом якихось обчислень, бібліотека PropTypes може заощадити розробнику купу часу.
Інтуїтивно зрозумілі назви змінних і жодних скорочень
Тут все зрозуміло із заголовка. Наприклад, назва UserEdBtn є поганим прикладом, а UserEditButton є хорошим прикладом. Не заощаджуйте на довжині назв компонентів. Вони повинні ясно та чітко відображати їхню функціональність, щоб ми не мусили шукати по всьому проєкту, що саме робить ця змінна і звідки вона береться.
Наприклад, FooterSocialLinksTitle є нормальною назвою, але не треба перебільшувати. FooterMenuSocialLinksTitleText — вже занадто довга назва.
Так само раджу чинити в циклах і методах:
Замість цього:
Писати так*:
*Використовувати index в якості key в реальних проєктах — погана практика, особливо якщо кількість елементів може динамічно змінюватись. Таке можна робити тільки у випадках, коли ми знаємо, що наш масив жодним чином не буде змінений.
В інших випадках для key треба шукати альтернативні варіанти значень. Якщо ми знаємо, що listItem.text у нас ніколи не повторюється, то краще використати його. А якщо ми самі створюємо масив, за яким відмальовуємо елементи, то в обʼєкт з елементом можна додати id і його вже використовувати в якості key.
Це все базовий мінімальний набір правил, щоб зробити проєкт максимально підтримуваним. Існує ще дуже багато інструментів і практик, окрім вищеперерахованих, але початківцю забивати ними голову поки не варто.
Якщо у когось є якісь коментарі, питання, зауваження чи пропозиції, пишіть у коментарях або ж мені в телеграм.
Усі статті, обговорення, новини про Front-end — в одному місці. Підписуйтеся на телеграм-канал!
20 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів