Тема: Абстракція бізнес-логіки. Створення кастомних хуків для повторного використання логіки (наприклад, хук для визначення розміру вікна або стану онлайн/офлайн).
Мета: Зрозуміти різницю між візуальним компонентом (UI Component) та відокремленою бізнес-логікою; навчитися ідентифікувати дубльований код у компонентах та виносити його у власні (Custom) хуки згідно з конвенцією найменування (useSomething); реалізувати підписки на глобальні події браузера (window.addEventListener) з коректною очисткою пам’яті через useEffect.
Необхідні інструменти: Браузер, редактор VS Code, Node.js, локально запущений проєкт єдиного репозиторію.
useWindowSize для визначення розмірів екрану.useOnlineStatus для моніторингу підключення до мережі.useWindowSizeУявіть, що у вашому додатку як Стрічка новин (Лабораторна 6), так і Панель керування (Практична 5) повинні знати поточну ширину екрану користувача, щоб змінити свій макет з Grid на Column при ширині менше 768px.Замість того, щоб копіювати useEffect з window.addEventListener('resize') у кожен з цих компонентів, ми винесемо цю логіку в хук.
Створіть файл src/hooks/useWindowSize.js (або .ts):
import { useState, useEffect } from "react";
const useWindowSize = () => {
// Локальний стан нашого хука
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
// Функція-обробник події
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
// Підписуємося на подію зміни розміру вікна
window.addEventListener("resize", handleResize);
// ОЧИЩЕННЯ (Cleanup) - критично важливо для уникнення Memory Leaks
return () => {
window.removeEventListener("resize", handleResize);
};
}, []); // Пустий масив: ефект спрацьовує 1 раз при монтуванні
// Хук "висилає" назовні свій стан
return windowSize;
};
export default useWindowSize;
useWindowSize у компонентіТепер відкрийте вашу NewsFeed.jsx або Dashboard.jsx. Використаємо новий хук майже так само, як ми використовуємо вбудований useState.
// src/pages/NewsFeed.jsx (Фрагмент коду)
import React from "react";
import useWindowSize from "../hooks/useWindowSize";
const NewsFeed = () => {
// Викликаємо наш кастомний хук. Він поверне об'єкт із width та height
const { width } = useWindowSize();
// Визначаємо, чи екран мобільний
const isMobile = width < 768;
return (
<div style=>
<h2>Стрічка Новин</h2>
{/* Умовний рендеринг: показуємо банер тільки на мобільних пристроях */}
{isMobile && (
<div
style=
>
📱 Ви переглядаєте мобільну версію
</div>
)}
{/* Адаптивна сітка CSS (inline для прикладу) */}
<div
style=
>
{/* ...тут рендер карток постів... */}
</div>
</div>
);
};
Спробуйте змінити розмір вікна браузера і переконайтесь, що компонент миттєво реагує на це.
useOnlineStatusОскільки наш додаток (NewsFeed) завантажує дані з Інтернету (JSONPlaceholder), було б чудово показувати користувачу банер “Немає підключення до Інтернету”, якщо він раптово зайшов у метро чи тунель і мережа зникла.
Створіть файл src/hooks/useOnlineStatus.js:
import { useState, useEffect } from "react";
const useOnlineStatus = () => {
// navigator.onLine - базова властивість браузера (true/false)
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
return isOnline;
};
export default useOnlineStatus;
Відкрийте кореневий компонент вашого проєкту (наприклад App.jsx або Layout).
// src/App.jsx (Фрагмент)
import useOnlineStatus from "./hooks/useOnlineStatus";
// інші імпорти...
function App() {
const isOnline = useOnlineStatus();
return (
<>
{/* Якщо користувач офлайн - виводимо червону смужку на весь екран зверху */}
{!isOnline && (
<div
style=
>
⚠️ Відсутнє підключення до Інтернету. Деякі функції можуть бути
недоступні.
</div>
)}
{/* Основні Routes вашого додатку */}
<main>{/* ... */}</main>
</>
);
}
Щоб протестувати це:
useThemeContext (якщо використовували Context для теми), або популярний хук useLocalStorage.useLocalStorage: він має приймати key (рядок) та initialValue (початкове значення). Він повинен повертати масив [value, setValue], за аналогією з вбудованим useState.useLocalStorage використайте магію useEffect для того, щоб кожного разу, коли змінюється value, хук автоматично зберігав це нове значення в браузерний localStorage (за допомогою JSON.stringify).useLocalStorage замість звичайного useState у вашому додатку для якогось поля (наприклад, для збереження теми день/ніч, або для імені користувача у простій формі), щоб після F5 сторінка “пам’ятала” вибір користувача.windowSize (пам’ять) два різні компоненти, якщо вони обидва викликали всередині себе useWindowSize()? (Поясніть, хук це інстанс чи виклик функції).useOnlineStatus, чому ми використовували саме масив залежностей [] для useEffect? Що сталося б, якби ми взагалі не передали другий аргумент в useEffect?return () => window.removeEventListener(...)) всередині хука windowSize, якщо компонент-споживач (наприклад, сторінка налаштувань) постійно монтується і розмонтовується під час навігації користувача?