Feature Flags у React без хаосу з умовами
Практична архітектура feature flags у React, яка ізолює логіку розгортання, зберігає типобезпеку та спрощує тестування й видалення експериментів.
Нова функція готова, але продуктова команда хоче ввімкнути її лише для внутрішніх користувачів, одного сегмента клієнтів і невеликого відсотка трафіку. Найшвидше рішення зазвичай полягає в тому, щоб додати кілька перевірок змінних середовища й розкидати умови по дереву компонентів. Через три місяці вже незрозуміло, який прапорець досі активний, які комбінації допустимі та чи не зламає видалення експерименту сусідній екран.
Feature flags складні не тому, що булеве значення складне. Проблеми починаються, коли політика розгортання, UI-розгалуження, права доступу, аналітика й очищення старого коду стають частиною звичайного рендерингу компонентів. Мета не в тому, щоб уникати умовного рендерингу. Мета — не перетворити продуктові рішення на невидиму архітектуру.
Відокремлюйте оцінювання прапорців від рендерингу UI
Компонент має ставити сфокусоване питання на кшталт «чи потрібно показувати оновлений checkout?». Він не повинен знати, чи відповідь отримано зі змінної середовища, віддаленого сервісу конфігурації, сегмента користувача або правила відсоткового розгортання. Таке рішення має належати окремій межі застосунку.
Почніть із типізованої моделі прапорців і однієї функції оцінювання. Не використовуйте сиру конфігурацію безпосередньо в JSX — повертайте стабільний об’єкт, який зможе споживати решта застосунку.
type FeatureFlags = {
redesignedCheckout: boolean;
bulkActions: boolean;
};
type FlagContext = {
userId: string;
plan: "free" | "pro" | "enterprise";
};
export function evaluateFlags(context: FlagContext): FeatureFlags {
return {
redesignedCheckout: context.plan !== "free",
bulkActions: context.plan === "enterprise"
};
}Ця функція навмисно виглядає просто. Це перевага: її можна тестувати без рендерингу React, а політика залишається видимою в одному місці. Якщо застосунок використовує віддалений сервіс, адаптер може перетворити його дані на цю саму типізовану форму.
Не називайте кожну умову feature flag
Під словом flag часто змішують кілька різних типів рішень. У них різні власники й різний життєвий цикл.
| Рішення | Найкраще місце | Типовий життєвий цикл |
|---|---|---|
| Розгортання функції | Шар оцінювання прапорців | Дні або місяці |
| Право користувача | Шар авторизації або capabilities | Тривалий |
| Стан завантаження чи помилки | Стан компонента | Секунди або хвилини |
| Варіант експерименту | Сервіс призначення варіантів | До завершення експерименту |
| Можливість платформи | Перевірка runtime-можливостей | До зміни підтримки браузером або пристроєм |
Перевірка прав відповідає на питання, чи може користувач виконати дію. Feature flag відповідає на питання, чи потрібно взагалі показувати продуктову можливість. Обидва рішення можуть впливати на UI, але об’єднання їх в універсальне `isEnabled` значно ускладнює аудит.
Створіть невелику межу на рівні застосунку
Передавання прапорців через багато шарів компонентів створює зайвий prop noise. Читання глобального сервісу прапорців у кожному компоненті породжує приховані залежності. Невеликий provider або application hook дає компонентам передбачуваний інтерфейс і водночас залишає оцінювання близько до межі застосунку.
const FeatureFlagsContext =
createContext<FeatureFlags | null>(null);
export function useFeatureFlags() {
const flags = useContext(FeatureFlagsContext);
if (!flags) {
throw new Error("FeatureFlagsProvider is missing");
}
return flags;
}
function CheckoutPage() {
const { redesignedCheckout } = useFeatureFlags();
return redesignedCheckout
? <RedesignedCheckout />
: <LegacyCheckout />;
}Хук не має приховувати кожне продуктове рішення. Він дає застосунку єдине місце для отримання поточного стану прапорців і змушує конфігураційну помилку проявитися одразу. У складному застосунку provider також може надавати стан завантаження та синхронізації, але ці стани мають бути явними, а не представленими як `undefined`, розкиданий по всьому дереву.
Робіть стани розгортання явними
Віддалені прапорці створюють проблему часу. Застосунок може відрендеритися до завершення запиту конфігурації. Якщо значення за замовчуванням — false, користувач може на мить побачити старий інтерфейс, а потім новий. Якщо true — незавершена функція може випадково стати доступною.
- Використовуйте безпечне значення за замовчуванням, якщо функція впливає на безпеку, цілісність даних або білінг.
- Не рендеріть чутливу гілку, доки необхідні дані про прапорці не стали відомими.
- Визначте, чи потрібні loading state, server-side evaluation або стабільний bootstrap payload.
- Синхронізуйте зміни прапорців з аналітикою, щоб подію можна було інтерпретувати разом з активним варіантом.
Правильний вибір залежить від функції. Косметична зміна навігації може пережити fallback. Натомість небезпечна масова дія не повинна з’являтися лише тому, що запит конфігурації завершився помилкою.
Тестуйте рішення, а не лише гілки
Типовий набір тестів перевіряє, що обидва компоненти рендеряться, але не перевіряє, чи отримує правильна аудиторія правильний варіант. Цінніші тести окремо перевіряють політику оцінювання та контракт рендерингу.
- Перевірте оцінювання прапорців для кожного підтримуваного плану, сегмента або правила розгортання.
- Перевірте, що provider передає споживачам оцінений результат.
- Перевірте enabled і disabled шляхи для важливих користувацьких сценаріїв.
- Перевірте невідомий або loading state, якщо конфігурація віддалена.
- Перевірте сценарій видалення до того, як видаляти прапорець і його fallback.
Це особливо важливо, коли прапорці взаємодіють. Два окремо коректні прапорці можуть створити недійсну комбінацію. Якщо комбінації мають значення, моделюйте їх як стан або політику, а не дозволяйте кожному компоненту самостійно виводити зв’язок. Підхід з явними станами, описаний у статті React State Machines: Replace Boolean Flags With Explicit UI States, корисний, коли прапорців стає більше, ніж кілька незалежних перемикачів.
Плануйте видалення ще до додавання прапорця
Feature flag — це тимчасова архітектура, якщо її навмисно не перетворено на постійну конфігурацію. Кожен новий прапорець має мати власника, умову видалення, дату створення й коротку нотатку про гілки, які він контролює. Простий інвентар не дає забутим прапорцям перетворитися на постійну поведінку продукту.
- Називайте прапорці за capability, а не за ticket або датою релізу.
- Уникайте негативних назв на кшталт `disableNewCheckout`: подвійні заперечення складніше перевіряти під час code review.
- Залишайте стару гілку доступною, доки не завершені telemetry та acceptance checks.
- Видаляйте прапорець, мертвий код, тести, конфігурацію й документацію однією зміною.
- Перед завершенням очищення шукайте ім’я прапорця по всьому репозиторію.
Очищення — це не просто наведення порядку в коді. Воно зменшує кількість можливих станів UI й спрощує міркування про майбутні зміни. Якщо прапорець неможливо видалити, бо дві версії мають співіснувати безстроково, можливо, це вже продуктова можливість або permission, а не тимчасовий прапорець розгортання.
Використовуйте прапорці як межі, а не як аварійні виходи
Feature flags добре працюють, коли ізолюють зміну, яку можна ввімкнути, спостерігати й видалити. Вони працюють погано, коли стають способом назавжди тримати дві несумісні архітектури всередині кожного компонента. Розміщуйте умову близько до змістовної межі застосунку, типізуйте політику й робіть життєвий цикл видимим.
Для команд, які підтримують велику React-кодову базу, така дисципліна безпосередньо пов’язана з модернізацією legacy-проєктів: зменшенням прихованого зв’язування, явним описом runtime-поведінки та безпечним видаленням застарілих шляхів. Якщо застосунок має багато незалежних продуктових поверхонь, команда для складного вебзастосунку допоможе вибудувати ці межі без перетворення кожного релізу на переписування.
Поширені запитання
Чи повинні feature flags зберігатися в React context?
Context зручний як механізм доставки оцінених прапорців, особливо коли вони потрібні багатьом компонентам. Політика оцінювання має залишатися поза компонентами, щоб її можна було окремо тестувати й замінювати.
Чи має feature flag також обробляти права користувача?
Зазвичай ні. Rollout контролює доступність capability, а permission — те, що користувачеві дозволено виконувати. Розділення цих понять спрощує security review і подальше очищення.
Коли потрібно видаляти feature flag?
Видаляйте його після завершення rollout, коли стара гілка більше не потрібна, а telemetry або acceptance checks підтверджують новий шлях. Прапорець, fallback, тести та конфігурацію краще видаляти однією зміною.