Контроль сложности компонентов React с помощью инверсии управления
Наверняка в какой-то момент вы обнаруживали, что сидите за столом, загроможденным случайными вещами... Как же до этого дошло? Медленно, но верно вы размещали все больше и больше вещей на идеально аккуратном столе, пока это не превратилось в мучение.
Это похоже на то, что происходит в компонентах React. Мы начинаем просто и чисто, но по мере добавления новых функций код становится грязным и сложным для поддержки.
Давайте посмотрим, как мы можем предотвратить это.
Начнем с (упрощенного) запроса на подтверждение, как этот:
const ConfirmationPrompt = () => (
<div>
<p>Are you sure?</p>
<div>
<button>Cancel</button>
<button>Confirm</button>
</div>
</div>
)
// Used as <ConfirmationPrompt />
Позже мы обнаружим, что запрашиваем подтверждение для выполнения действия, критически важного для нашей системы. Мы можем добавить предупреждающее сообщение, чтобы подчеркнуть его важность.
const ConfirmationPrompt = ({destructive}) => (
<div>
{destructive && (<p>☠️ DANGER ☠️</p>)}
<p>Are you sure?</p>
<div>
<button>Cancel</button>
<button>Confirm</button>
</div>
</div>
)
// Used as
<ConfirmationPrompt destructive />
Все вроде бы хорошо, но что, если некоторые действия опасны и должны инициировать уведомление по электронной почте? Мы могли бы просто скопировать ту же логику, но заметили, что быстро столкнулись с одной большой проблемой:
Возможные вариации нашего компонента растут в геометрической прогрессии.
const ConfirmationPrompt = ({destructive, triggersNotifications}) => (
<div>
{triggersNotifications && (<p>ℹ️ Email will be sent</p>)}
{destructive && (<p>☠️ DANGER ☠️</p>)}
<p>Are you sure?</p>
<div>
<button>Cancel</button>
<button>Confirm</button>
</div>
</div>
)
// Used as
<ConfirmationPrompt
destructive
triggersNotifications
/>
В нашем первом примере было 1 возможное состояние, во втором - 2 (разрушающее и неразрушающее), а в третьем - уже 4!
Помните: шаблоны сохраняются, а код, который изменился, склонен продолжать меняться, поэтому лучше решить эту проблему как можно раньше.
А вот здесь все становится интереснее. Вместо того чтобы компонент действовал как читатель мыслей, угадывая, что нужно другим частям кода из ConfirmationPrompt, мы можем перевернуть сценарий и заставить их решать!
Этот простой и мощный инструмент называется инверсией контроля.
По сути, мы возвращаем контроль пользователю нашего компонента, делая его более гибким и адаптируемым.
Выглядит это следующим образом:
const ConfirmationPrompt = ({header}) => (
<div>
{header} //Our caller decides what to put here
<p>Are you sure?</p>
<div>
<button>Cancel</button>
<button>Confirm</button>
</div>
</div>
)
// Used as
<ConfirmationPrompt
header={
<>
<WillNotifyMessage/>
<DangerZoneMessage/>
</>
}
/>
// or
<ConfirmationPrompt
header={<WillNotifyMessage/>}
/>
// and any other use case that comes up!
Отлично! Наш оригинальный компонент стал короче и лаконичнее, а остальная часть кодовой базы избавлена от необходимости знать, как должен быть настроен ConfirmationPrompt.
К сожалению, это не поможет нам сохранить чистоту рабочего стола, но инверсия контроля имеет множество применений в React, и вы найдете примеры с различными вариациями. Идея, лежащая в ее основе, всегда одна и та же - вернуть управление вызывающей функции.