Примітка: деякі приклади коду будуть базуватися на React. Однак концепції та патерни універсальні і не прив’язані до конкретного фреймворку.
Нічого не триває вічно
Під час роботи над проектом дуже часто ми створюємо компоненти для досягнення конкретної мети, слідуючи інструкціям клієнта. Однак, коли з’являються нові вимоги, часто потрібно внести невеликі зміни, які можуть в результаті призвести до того, що компонент перестане працювати. Настає момент, коли простіше створити новий компонент, ніж повторно використовувати вже наявний. То як уникнути цього?
Ранні етапи
Для вирішення цієї проблеми можна взяти як приклад простий і широко використовуваний компонент під назвою Input
. На початку проекту наш клієнт повідомляє, що компонент матиме два варіанти: стандартний та з обводкою, а також текст як placeholder. Як розумні фронтенд-розробники, ми, ймовірно, створимо щось подібне:
На даному етапі наш Input
задовольняє всі вимоги і здається простим у використанні на будь-якій сторінці додатку. За допомогою не більше ніж 5 рядків коду, ми маємо функціональний компонент.
Тепер клієнт хоче, щоб іконка пошуку з'являлася зліва всередині інпуту в деяких областях додатку. Це здається не надто складним; ми можемо додати новий пропс, який вказуватиме, яку іконку використовувати, і компонент сам з цим впорається:
Добре, це вирішує нашу проблему, і клієнт задоволений.
Після презентації продукту дизайнери розуміють, що деякі інпути мають вказувати валюту з ярликом праворуч, наприклад USD. Щоб вирішити це, ми можемо додати новий пропс для вказівки валюти.
Ще одна проблема вирішена.
Збільшення складності
Як ми бачимо, ці зміни можуть здаватися незначними для кожної взаємодії з компонентом, але ми починаємо помічати, як складність росте.
Тепер, після демонстрації продукту потенційним покупцям, клієнт прислухається до відгуків команди і вирішує, що було б добре, якщо користувач міг би вибрати валюту в тому ж самому Input. Іншими словами, створити своєрідний селектор, в якому можна вибрати валюту.
Це вже передбачає нову функціональність, і хоча є різні підходи для вирішення цієї задачі, як швидкий і не дуже вишуканий спосіб реалізувати компонент якнайшвидше, ми додаємо “селектор” як пропс із усіма доступними валютами.
Здається, що це працює, і те, чого UI не розуміє, не може завдати шкоди, правда?
Ну, проблема в тому, що код починає рости таким чином, що адаптація до нових вимог тепер може здатися гірським піком, який нам доведеться подолати.
Уявімо, наприклад, що на новій сторінці нам потрібно використовувати Input
тільки як інпут для пароля. Що ми робимо з усіма іншими пропсами, які ми не будемо використовувати? Ми можемо просто їх ігнорувати, але компонент має бути готовий до таких випадків і належно обробляти кожну взаємодію.
Трохи пізно
Уявімо, що наступна вимога — іконка в Input
повинна адаптуватися під вибрану валюту.
Отже, якщо я вибираю EUR, я маю очікувати, що символ “€” з'явиться на початку інпуту.
Чи слід передавати символи для кожної валюти через окремий пропс і відображати їх залежно від вибраної валюти?
А як щодо іконки “search.svg”, яку ми інтегрували на початку?
На цьому етапі, з новими вимогами, ми стикаємося з такими варіантами:
- Спробувати інтегрувати селектор і оновити іконку в одному компоненті (це включатиме багато умов та складну логіку).
- Відокремити
Input
від інших типів інпутів, назвавши йогоInputCurrencySelector
. Це призведе до дублювання стилістичної логіки в різних частинах додатку. - Повністю відмовитися від компонента
Input
і створити новий —NewInput
.
У кожному з цих випадків ми вводимо технічний борг, і крива входження росте.
Що сталося по ходу?
Що сталося тут, так це те, що ми розвинули емоційну прив’язаність до нашого компонента. Ми полюбили його з самого початку і не хочемо з ним розлучатися. Замість того, щоб відпустити, ми намагаємося зберегти його через постійно змінювані вимоги дизайну, адаптуючи його під нові умови.
Як і в будь-яких токсичних стосунках, ми маємо відпустити його.
Давайте подумаємо, яка насправді мета інпуту? Дивлячись з боку HTML
, єдине, що ми маємо отримати від інпуту, це інформація, яку надає користувач. І нічого більше.
Компонент Input
повинен фокусуватися саме на цьому, без турбот щодо іконок чи селекторів.
Основна проблема, що виникає через ці погані стосунки з вашим компонентом, полягає в тому, що пропси переважно визначають вміст, а не сутність самого компонента.
Вміст Input
не повинен визначатися через пропси. Якби ми зберегли основні обов'язки елемента HTML
інпуту, структура нашого Input
була б зовсім іншою.
Примітка: ми залишили variant як він є, як це визначено в самому компоненті.
Якщо нам потрібно додати іконку (неважливо, яка саме), ми можемо зробити це незалежно від нашого компонента Input
.
Якщо нам також потрібен селектор, ми можемо працювати над ним в окремому компоненті.
setSelectedCurrrency(currency)}
/>
Таким чином, ми розділяємо обов'язки кожного компонента і зводимо їх до їхніх основних завдань:
Icon
відображає іконкуCurrencySelector
лише відповідає за повернення вибраної валютиInput
обробляє введені користувачем дані
Додатково, ми можемо додати елемент `` для правильного розташування елементів.
Якщо хочемо, ми можемо також ізолювати цей компонент окремо як CurrencyInput
і використовувати його всюди. Однак у цьому випадку ми обмежуємо його використання лише для цієї конкретної мети.
Якщо любите їх, відпустіть
Важливо не ставати надто прив’язаними до своїх компонентів. Часто ми вимагаємо від них занадто багато і обтяжуємо їх обов'язками, які виходять за межі їхньої основної функції. Ми ставимо їх у роль героїв, які повинні впоратися з усім і завжди адаптуватися до нових потреб. Важливо ізолювати функціональність, роблячи наші компоненти більш гнучкими і готовими до адаптації до будь-яких змін, що можуть статися.
Перекладено з: Don’t build lasting relationships with your components