One hook to rule them all
Мене завжди бентежив підхід, коли компонент бере на себе й логіку, і ефекти, і обробку стану — усе підряд.
Десь ми беремо якісь дані, створюємо змінні з похідними станами, описуємо колбеки і хендлери, ефекти і тому подібне. І якщо описувати це в самому компоненті, то він перетворюється на чорну скриньку, бо тоді єдине місце, де ми можемо перевірити чи правильні значення у нас у стейті — це відображення цих даних в HTML.
І це мені дуже не подобається. Бо я вважаю, що логіку треба тестувати окремо, як логіку, а в самому компоненті ми маємо перевіряти лише те, чи вірно відображаються дані в залежності від різних станів.
Тому я користуюсь далеко не інноваційним, не оригінальним, проте дуже зручним підходом state hook. Він полягає в тому, що усі сторонні дані, які потрібні компоненту, я отримую й обробляю в хуці, який має назву
use<ComponentName>State.І повертає цей хук виключно стан, який необхідний для рендеру UI. Таким чином компонент отримує виключно ті дані, які будуть в JSX. І навіть якщо для цього стану потрібен якийсь інший, сам компонент про них не знатиме.
Це дозволяє дуже чітко розділити відповідальності — всередині стейт-хука я роблю усі обчислення, перетворення за необхідності, створюю усі хендлери та ініціалізую всі ефекти, а UI-компонент просто перетворює цей "зліпок" на інтерфейс.
function useComponentState() {
const [filter, setFilter] = useState('');
const { data, isLoading } = useSomeFetch(filter);
const isDisabled = isLoading || !data?.length;
const displayList = useMemo(() => {
return data.map(doSomeStuff);
}, [data]);
const onInputChange = useCallback((event) => {
setFilter(trim(event.target.value));
});
return {
filter,
displayList,
isDisabled,
onInputChange
}
}
function Component() {
const {
filter,
displayList,
isDisabled,
onInputChange
} = useComponentState();
return (
<>
<input value={filter} onChange={onInputChange} />
<List items={displayList} />
<button disabled={isDisabled}>Click me</button>
</>
)
}
Що це мені дає? По-перше, розділення відповідальностей. По-друге — компонент перестає бути чорною скринькою і перетворюється на абсолютно прямолінійну систему.
Це полегшує тестування. Бо я тепер можу тестувати суто стейт-хук як юніт, перевіряючи значення замість відображення. А саме відображення можна потестувати просто замокавши цей хук кількома потрібними "снапшотами" стану.
Це полегшує контроль за залежностями компонента, бо у нього вона стає лише одна. А залежності стейт-хука це вже геть інша історія.
Я не скажу, що це срібна куля і панацея, яка вирішує усі проблеми. Бо навіть інтеграційні тести все одно вимагатимуть від нас перевірки різних сценаріїв на відображенні. Але на рівні суто компонента такий підхід суттєво мені спростив роботу. Як мінімум, я не харюсь через те, що всередині компонента купа заплутаної логіки, бо її там просто немає.
Взагалі це лише частина загального підходу з розділення логіки на декілька шарів, зокрема аж на чотири, який я запропонував команді, і який знайшов дуже теплий відгук серед колег.
Цікавить, як це працює на рівні чотирьох шарів? Дайте знати в коментарях — розповім про весь підхід, який у нас суттєво покращив якість як суто коду, так і побудови наших інтерфейсів.
@babichdev