Розкрийте силу логіки для повторного використання у React за допомогою кастомних хуків. Навчіться створювати їх для чистого коду, який легше підтримувати.
Кастомні хуки: Патерни повторно використовуваної логіки в React
Хуки React здійснили революцію в написанні компонентів React, додавши стан та функції життєвого циклу до функціональних компонентів. Серед багатьох переваг, які вони пропонують, кастомні хуки виділяються як потужний механізм для винесення та повторного використання логіки між кількома компонентами. Ця стаття глибоко занурить вас у світ кастомних хуків, розглядаючи їхні переваги, створення та використання на практичних прикладах.
Що таке кастомні хуки?
По суті, кастомний хук — це функція JavaScript, назва якої починається зі слова "use" і яка може викликати інші хуки. Вони дозволяють виносити логіку компонента у функції для повторного використання. Це потужний спосіб ділитися логікою зі станом, побічними ефектами чи іншою складною поведінкою між компонентами, не вдаючись до рендер-пропсів, компонентів вищого порядку чи інших складних патернів.
Ключові характеристики кастомних хуків:
- Правило іменування: Назви кастомних хуків повинні починатися зі слова "use". Це сигналізує React, що функція містить хуки й повинна дотримуватися правил хуків.
- Повторне використання: Основна мета — інкапсулювати логіку для повторного використання, що полегшує обмін функціональністю між компонентами.
- Логіка зі станом: Кастомні хуки можуть керувати власним станом за допомогою хука
useState
, що дозволяє інкапсулювати складну поведінку зі станом. - Побічні ефекти: Вони також можуть виконувати побічні ефекти за допомогою хука
useEffect
, що дозволяє інтеграцію із зовнішніми API, завантаження даних тощо. - Компонування: Кастомні хуки можуть викликати інші хуки, що дозволяє створювати складну логіку шляхом компонування менших, більш сфокусованих хуків.
Переваги використання кастомних хуків
Кастомні хуки пропонують кілька значних переваг у розробці на React:
- Повторне використання коду: Найбільш очевидною перевагою є можливість повторно використовувати логіку в кількох компонентах. Це зменшує дублювання коду та сприяє дотриманню принципу DRY (Don't Repeat Yourself).
- Покращена читабельність: Виносячи складну логіку в окремі кастомні хуки, ваші компоненти стають чистішими та простішими для розуміння. Основна логіка компонента залишається зосередженою на рендерингу UI.
- Полегшена підтримка: Коли логіка інкапсульована в кастомних хуках, зміни та виправлення помилок можна застосовувати в одному місці, зменшуючи ризик внесення помилок у кілька компонентів.
- Тестованість: Кастомні хуки можна легко тестувати ізольовано, гарантуючи, що логіка для повторного використання працює коректно незалежно від компонентів, які її використовують.
- Спрощені компоненти: Кастомні хуки допомагають "розвантажити" компоненти, роблячи їх менш багатослівними та більш зосередженими на своїй основній меті.
Створення вашого першого кастомного хука
Проілюструємо створення кастомного хука на практичному прикладі: хук, який відстежує розмір вікна.
Приклад: useWindowSize
Цей хук повертатиме поточну ширину та висоту вікна браузера. Він також оновлюватиме ці значення при зміні розміру вікна.
import { useState, useEffect } from 'react';
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
// Видаляємо слухач події під час очищення
return () => window.removeEventListener('resize', handleResize);
}, []); // Порожній масив гарантує, що ефект виконується лише при монтуванні
return windowSize;
}
export default useWindowSize;
Пояснення:
- Імпорт необхідних хуків: Ми імпортуємо
useState
таuseEffect
з React. - Визначення хука: Ми створюємо функцію з назвою
useWindowSize
, дотримуючись правила іменування. - Ініціалізація стану: Ми використовуємо
useState
для ініціалізації стануwindowSize
початковою шириною та висотою вікна. - Налаштування слухача подій: Ми використовуємо
useEffect
, щоб додати до вікна слухач події resize. Коли розмір вікна змінюється, функціяhandleResize
оновлює станwindowSize
. - Очищення: Ми повертаємо функцію очищення з
useEffect
, щоб видалити слухач події, коли компонент демонтується. Це запобігає витокам пам'яті. - Повернення значень: Хук повертає об'єкт
windowSize
, що містить поточну ширину та висоту вікна.
Використання кастомного хука в компоненті
Тепер, коли ми створили наш кастомний хук, подивімося, як його використовувати в компоненті React.
import React from 'react';
import useWindowSize from './useWindowSize';
function MyComponent() {
const { width, height } = useWindowSize();
return (
Ширина вікна: {width}px
Висота вікна: {height}px
);
}
export default MyComponent;
Пояснення:
- Імпорт хука: Ми імпортуємо кастомний хук
useWindowSize
. - Виклик хука: Ми викликаємо хук
useWindowSize
всередині компонента. - Доступ до значень: Ми деструктуруємо повернутий об'єкт, щоб отримати значення
width
таheight
. - Рендеринг значень: Ми рендеримо значення ширини та висоти в UI компонента.
Будь-який компонент, що використовує useWindowSize
, буде автоматично оновлюватися при зміні розміру вікна.
Більш складні приклади
Розглянемо деякі більш просунуті випадки використання кастомних хуків.
Приклад: useLocalStorage
Цей хук дозволяє легко зберігати та отримувати дані з локального сховища.
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
// Стан для зберігання нашого значення
// Передаємо початкове значення в useState, щоб логіка виконувалася лише один раз
const [storedValue, setStoredValue] = useState(() => {
try {
// Отримуємо з локального сховища за ключем
const item = window.localStorage.getItem(key);
// Парсимо збережений json або повертаємо initialValue, якщо його немає
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// У разі помилки також повертаємо initialValue
console.log(error);
return initialValue;
}
});
// Повертаємо обгорнуту версію сеттер-функції useState, яка...
// ...зберігає нове значення в localStorage.
const setValue = (value) => {
try {
// Дозволяємо значенню бути функцією, щоб мати такий самий API, як у useState
const valueToStore = value instanceof Function ? value(storedValue) : value;
// Зберігаємо в локальне сховище
window.localStorage.setItem(key, JSON.stringify(valueToStore));
// Зберігаємо стан
setStoredValue(valueToStore);
} catch (error) {
// Більш просунута реалізація обробила б випадок помилки
console.log(error);
}
};
useEffect(() => {
try {
const item = window.localStorage.getItem(key);
setStoredValue(item ? JSON.parse(item) : initialValue);
} catch (error) {
console.log(error);
}
}, [key, initialValue]);
return [storedValue, setValue];
}
export default useLocalStorage;
Використання:
import React from 'react';
import useLocalStorage from './useLocalStorage';
function MyComponent() {
const [name, setName] = useLocalStorage('name', 'Guest');
return (
Привіт, {name}!
setName(e.target.value)}
/>
);
}
export default MyComponent;
Приклад: useFetch
Цей хук інкапсулює логіку для завантаження даних з API.
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP помилка! статус: ${response.status}`);
}
const json = await response.json();
setData(json);
setLoading(false);
} catch (error) {
setError(error);
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Використання:
import React from 'react';
import useFetch from './useFetch';
function MyComponent() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/todos/1');
if (loading) return Завантаження...
;
if (error) return Помилка: {error.message}
;
return (
Заголовок: {data.title}
Завершено: {data.completed ? 'Так' : 'Ні'}
);
}
export default MyComponent;
Найкращі практики для кастомних хуків
Щоб ваші кастомні хуки були ефективними та легкими в підтримці, дотримуйтесь цих найкращих практик:
- Зберігайте сфокусованість: Кожен кастомний хук повинен мати єдину, чітко визначену мету. Уникайте створення надто складних хуків, які намагаються робити забагато.
- Документуйте свої хуки: Надавайте чітку та стислу документацію для кожного кастомного хука, пояснюючи його призначення, вхідні та вихідні дані.
- Тестуйте свої хуки: Пишіть юніт-тести для своїх кастомних хуків, щоб переконатися, що вони працюють коректно та надійно.
- Використовуйте описові назви: Обирайте для своїх кастомних хуків назви, які чітко вказують на їхнє призначення.
- Витончено обробляйте помилки: Реалізуйте обробку помилок у ваших кастомних хуках, щоб запобігти непередбачуваній поведінці та надавати інформативні повідомлення про помилки.
- Враховуйте можливість повторного використання: Проєктуйте свої кастомні хуки з думкою про повторне використання. Робіть їх достатньо загальними, щоб їх можна було використовувати в кількох компонентах.
- Уникайте надмірної абстракції: Не створюйте кастомні хуки для простої логіки, яку можна легко реалізувати всередині компонента. Виносьте лише ту логіку, яка дійсно є складною та призначена для повторного використання.
Поширені помилки, яких слід уникати
- Порушення правил хуків: Завжди викликайте хуки на верхньому рівні вашої функції кастомного хука і тільки з функціональних компонентів React або інших кастомних хуків.
- Ігнорування залежностей у useEffect: Переконайтеся, що ви включили всі необхідні залежності в масив залежностей хука
useEffect
, щоб запобігти застарілим замиканням та несподіваній поведінці. - Створення нескінченних циклів: Будьте обережні при оновленні стану всередині хука
useEffect
, оскільки це може легко призвести до нескінченних циклів. Переконайтеся, що оновлення є умовним і базується на змінах у залежностях. - Забування про очищення: Завжди включайте функцію очищення в
useEffect
, щоб видаляти слухачі подій, скасовувати підписки та виконувати інші завдання з очищення для запобігання витокам пам'яті.
Просунуті патерни
Компонування кастомних хуків
Кастомні хуки можна компонувати разом для створення більш складної логіки. Наприклад, ви можете поєднати хук useLocalStorage
з хуком useFetch
для автоматичного збереження завантажених даних у локальне сховище.
Обмін логікою між хуками
Якщо кілька кастомних хуків мають спільну логіку, ви можете винести цю логіку в окрему утилітарну функцію та повторно використовувати її в обох хуках.
Використання Context з кастомними хуками
Кастомні хуки можна використовувати разом із React Context для доступу та оновлення глобального стану. Це дозволяє створювати компоненти для повторного використання, які знають про глобальний стан додатка та можуть взаємодіяти з ним.
Приклади з реального світу
Ось кілька прикладів того, як кастомні хуки можна використовувати в реальних додатках:
- Валідація форм: Створіть хук
useForm
для обробки стану форми, валідації та відправки. - Автентифікація: Реалізуйте хук
useAuth
для керування автентифікацією та авторизацією користувача. - Управління темами: Розробіть хук
useTheme
для перемикання між різними темами (світла, темна тощо). - Геолокація: Створіть хук
useGeolocation
для відстеження поточного місцезнаходження користувача. - Виявлення прокрутки: Створіть хук
useScroll
для визначення, коли користувач прокрутив сторінку до певної точки.
Приклад: хук useGeolocation для крос-культурних застосунків, таких як картографічні сервіси або служби доставки
import { useState, useEffect } from 'react';
function useGeolocation() {
const [location, setLocation] = useState({
latitude: null,
longitude: null,
error: null,
});
useEffect(() => {
if (!navigator.geolocation) {
setLocation({
latitude: null,
longitude: null,
error: 'Геолокація не підтримується цим браузером.',
});
return;
}
const watchId = navigator.geolocation.watchPosition(
(position) => {
setLocation({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
error: null,
});
},
(error) => {
setLocation({
latitude: null,
longitude: null,
error: error.message,
});
}
);
return () => navigator.geolocation.clearWatch(watchId);
}, []);
return location;
}
export default useGeolocation;
Висновок
Кастомні хуки — це потужний інструмент для написання більш чистого, повторно використовуваного та легкого в підтримці коду на React. Інкапсулюючи складну логіку в кастомні хуки, ви можете спростити свої компоненти, зменшити дублювання коду та покращити загальну структуру ваших додатків. Використовуйте кастомні хуки та розкрийте їхній потенціал для створення більш надійних та масштабованих React-додатків.
Почніть з визначення ділянок у вашій існуючій кодовій базі, де логіка повторюється в кількох компонентах. Потім проведіть рефакторинг цієї логіки, винісши її в кастомні хуки. З часом ви створите бібліотеку хуків для повторного використання, яка прискорить процес розробки та покращить якість вашого коду.
Не забувайте дотримуватися найкращих практик, уникати поширених помилок і досліджувати просунуті патерни, щоб отримати максимальну користь від кастомних хуків. З практикою та досвідом ви станете майстром кастомних хуків та більш ефективним розробником React.