В React существует два основных правила использования хуков:
✅ Правило 1: Вызывайте хуки только на верхнем уровне
✅ Правило 2: Вызывайте хуки только в функциональных компонентах React или пользовательских хуках
Ключевое правило: Соблюдайте порядок вызова хуков и не вызывайте их условно! 🎯
Представьте, что хуки — это как ингредиенты в рецепте. Если вы каждый раз добавляете их в разном порядке или пропускаете некоторые, блюдо получится непредсказуемым! 🍳
React полагается на порядок вызова хуков для правильной работы:
// ✅ Правильно — хуки на верхнем уровне
function MyComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
// Эффект
}, []);
return (
<div>
<p>{count} - {name}</p>
<button onClick={() => setCount(count + 1)}>
Увеличить
</button>
</div>
);
}
// ❌ Неправильно — хуки в условиях
function BadComponent({ condition }) {
if (condition) {
const [state, setState] = useState(0); // ОШИБКА!
}
return <div>Компонент</div>;
}React использует внутренний список для отслеживания хуков:
// Как React видит хуки:
// Первый рендер:
// 1. useState(0) -> [0, setter]
// 2. useState('') -> ['', setter]
// 3. useEffect(() => {...}) -> undefined
// Второй рендер:
// 1. useState(0) -> [0, setter] // Тот же хук!
// 2. useState('') -> ['', setter] // Тот же хук!
// 3. useEffect(() => {...}) -> undefined // Тот же хук!// ❌ Неправильно - условный вызов
function BadComponent({ isLoggedIn }) {
const [user, setUser] = useState(null);
if (isLoggedIn) {
// Это нарушает порядок хуков!
const [token, setToken] = useState('');
}
return <div>Компонент</div>;
}
// ✅ Правильно - всегда вызывайте хуки
function GoodComponent({ isLoggedIn }) {
const [user, setUser] = useState(null);
const [token, setToken] = useState(''); // Всегда вызывается
// Используйте условную логику внутри хуков
useEffect(() => {
if (isLoggedIn) {
// Логика для авторизованных пользователей
}
}, [isLoggedIn]);
return <div>Компонент</div>;
}// ❌ Неправильно - хуки в циклах
function BadComponent({ items }) {
const [state, setState] = useState(null);
items.forEach(item => {
// Каждый рендер может иметь разное количество элементов!
const [itemState, setItemState] = useState(item); // ОШИБКА!
});
return <div>Компонент</div>;
}
// ✅ Правильно - хуки вне циклов
function GoodComponent({ items }) {
const [state, setState] = useState(null);
// Используйте массив состояний
const [itemStates, setItemStates] = useState(
items.map(item => item)
);
return (
<div>
{items.map((item, index) => (
<div key={index}>
<input
value={itemStates[index]}
onChange={e => {
const newStates = [...itemStates];
newStates[index] = e.target.value;
setItemStates(newStates);
}}
/>
</div>
))}
</div>
);
}// ❌ Неправильно - хуки в обычных функциях
function regularFunction() {
const [state, setState] = useState(0); // ОШИБКА!
return state;
}
// ✅ Правильно - хуки в компонентах или пользовательских хуках
function Component() {
const value = useCustomHook(); // Правильно
return <div>{value}</div>;
}
function useCustomHook() {
const [state, setState] = useState(0); // Правильно
return state;
}// ✅ Пользовательские хуки начинаются с "use"
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
// ❌ Неправильное именование
function counterHook() { // Должно начинаться с "use"
const [count, setCount] = useState(0);
return [count, setCount];
}// ✅ Правильное использование
function UserProfile() {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<p>Счетчик: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Сброс</button>
</div>
);
}
// ✅ Пользовательский хук для API
function useApi(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch(url);
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}// ❌ Слишком много отдельных состояний
function BadForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
// ... еще состояния
return (
<form>
{/* Поля формы */}
</form>
);
}
// ✅ Группировка в объект
function GoodForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
confirmPassword: ''
});
const updateField = (field, value) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
return (
<form>
<input
value={formData.name}
onChange={e => updateField('name', e.target.value)}
/>
<input
value={formData.email}
onChange={e => updateField('email', e.target.value)}
/>
{/* Остальные поля */}
</form>
);
}// ❌ Неоптимальный useEffect
function BadComponent({ userId, postId }) {
const [user, setUser] = useState(null);
const [post, setPost] = useState(null);
// Выполняется при изменении любого пропса
useEffect(() => {
fetchUser(userId).then(setUser);
fetchPost(postId).then(setPost);
}, [userId, postId]); // Оба пропса в зависимостях
return <div>Компонент</div>;
}
// ✅ Разделенные эффекты
function GoodComponent({ userId, postId }) {
const [user, setUser] = useState(null);
const [post, setPost] = useState(null);
// Отдельные эффекты для разных данных
useEffect(() => {
if (userId) {
fetchUser(userId).then(setUser);
}
}, [userId]); // Только userId
useEffect(() => {
if (postId) {
fetchPost(postId).then(setPost);
}
}, [postId]); // Только postId
return <div>Компонент</div>;
}// ✅ Простое состояние
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Счетчик: {count}
</button>
);
}// ✅ Подписки и очистка
function WindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
// Очистка подписки
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return (
<div>
Размер окна: {size.width} x {size.height}
</div>
);
}// ✅ Переиспользуемый хук
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// Использование
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'ru');
return (
<div>
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">Светлая</option>
<option value="dark">Темная</option>
</select>
<select value={language} onChange={e => setLanguage(e.target.value)}>
<option value="ru">Русский</option>
<option value="en">English</option>
</select>
</div>
);
}Правила хуков — это как правила дорожного движения для React! 🚦
Когда нарушать нельзя:
Практические советы:
Соблюдение правил хуков — залог стабильной и предсказуемой работы ваших React-приложений! 💪
Хотите больше полезных статей о React? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и прокачивайтесь каждый день! 🚀