Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Где нельзя использовать hooks в React
Hooks имеют строгие правила использования. Они работают только в определенных контекстах, и нарушение этих правил приведет к ошибкам и неожиданному поведению. Это важный вопрос, который показывает глубокое понимание React.
Основное правило: Rules of Hooks
Официальные правила React для hooks:
- Вызывай hooks только на верхнем уровне
- Вызывай hooks только в React компонентах и кастомных хуках
Место 1: Нельзя в обычных функциях
❌ НЕПРАВИЛЬНО: вызов в обычной функции
// Это обычная функция, не React компонент
function regularFunction() {
const [count, setCount] = useState(0) // ОШИБКА!
return count
}
// Результат: Runtime ошибка
// "React Hook "useState" is called in a function "regularFunction"
// that is neither a React function component nor a custom React Hook"
✅ ПРАВИЛЬНО: в React компоненте
// React компонент (начинается с заглавной буквы)
function RegularComponent() {
const [count, setCount] = useState(0) // OK!
return <div>{count}</div>
}
Место 2: Нельзя в условиях
❌ НЕПРАВИЛЬНО: hooks в if/else блоке
function BadComponent({ showCounter }) {
if (showCounter) {
const [count, setCount] = useState(0) // ОШИБКА!
}
return <div>...</div>
}
// Проблема: порядок вызовов hooks изменяется
// React не может нормально отследить состояние
✅ ПРАВИЛЬНО: state вне условия
function GoodComponent({ showCounter }) {
const [count, setCount] = useState(0) // На верхнем уровне
if (showCounter) {
return <div>{count}</div>
}
return <div>Счетчик скрыт</div>
}
Место 3: Нельзя в циклах
❌ НЕПРАВИЛЬНО: hooks в цикле
function BadList({ items }) {
// ОШИБКА: количество hooks зависит от items.length
for (let i = 0; i < items.length; i++) {
const [selected, setSelected] = useState(false)
}
return <ul>{/* ... */}</ul>
}
// Проблема: при изменении длины items нарушится порядок hooks
// React будет путать какой state какому элементу принадлежит
✅ ПРАВИЛЬНО: state в компоненте
function GoodList({ items }) {
const [selectedIds, setSelectedIds] = useState<string[]>([])
return (
<ul>
{items.map(item => (
<ListItem
key={item.id}
item={item}
isSelected={selectedIds.includes(item.id)}
onSelect={() => {
setSelectedIds(prev => [...prev, item.id])
}}
/>
))}
</ul>
)
}
Место 4: Нельзя в обработчиках событий
❌ НЕПРАВИЛЬНО: hooks в onClick/onChange
function BadForm() {
const handleChange = () => {
const [value, setValue] = useState("") // ОШИБКА!
setValue("new value")
}
return <input onChange={handleChange} />
}
// Проблема: hooks вызываются каждый раз при событии
// Состояние переинициализируется
✅ ПРАВИЛЬНО: state в компоненте, обработчик его использует
function GoodForm() {
const [value, setValue] = useState("") // На верхнем уровне
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value) // Используем state в обработчике
}
return <input value={value} onChange={handleChange} />
}
Место 5: Нельзя в try/catch блоках (условные)
❌ НЕПРАВИЛЬНО: hooks в try/catch
function BadComponent() {
try {
const [data, setData] = useState(null) // ОШИБКА (может быть скипнут)
} catch (error) {
const [error, setError] = useState(null) // ОШИБКА!
}
return <div></div>
}
✅ ПРАВИЛЬНО: состояние вне try/catch
function GoodComponent() {
const [data, setData] = useState(null) // На верхнем уровне
const [error, setError] = useState(null)
useEffect(() => {
try {
// Логика внутри эффекта
} catch (err) {
setError(err) // Используем setState
}
}, [])
return <div></div>
}
Место 6: Нельзя в классовых компонентах
❌ НЕПРАВИЛЬНО: hooks в классовом компоненте
class BadComponent extends React.Component {
render() {
const [count, setCount] = useState(0) // ОШИБКА!
return <div>{count}</div>
}
}
✅ ПРАВИЛЬНО: либо функциональный компонент, либо использовать lifecycle методы
// Функциональный компонент с hooks
function GoodComponent() {
const [count, setCount] = useState(0)
return <div>{count}</div>
}
// ИЛИ классовый компонент без hooks
class ClassComponent extends React.Component {
state = { count: 0 }
render() {
return <div>{this.state.count}</div>
}
}
Место 7: Нельзя в асинхронных функциях
❌ НЕПРАВИЛЬНО: hooks в async function
function BadComponent() {
// Функция async, это изменяет поведение
const handleAsync = async () => {
const [data, setData] = useState(null) // ОШИБКА!
}
return <button onClick={handleAsync}>Load</button>
}
✅ ПРАВИЛЬНО: hooks на верхнем уровне
function GoodComponent() {
const [data, setData] = useState(null) // На верхнем уровне
const handleAsync = async () => {
const response = await fetch("/api/data")
const result = await response.json()
setData(result) // Используем state setter
}
return <button onClick={handleAsync}>Load</button>
}
Сложный пример: нарушение правил
❌ НЕПРАВИЛЬНО: множество нарушений
function ComplexBadComponent({ users, showDetails }) {
const renderUser = (user) => {
// Это функция (не компонент)
const [expanded, setExpanded] = useState(false) // ОШИБКА 1
return (
<div>
{showDetails && (
// Условие
const [details, setDetails] = useState(null) // ОШИБКА 2
)}
{users.map(u => {
// Цикл
const [selected, setSelected] = useState(false) // ОШИБКА 3
return <div key={u.id}>{u.name}</div>
})}
</div>
)
}
return <div>{users.map(renderUser)}</div>
}
✅ ПРАВИЛЬНО: правильная структура
function ComplexGoodComponent({ users, showDetails }) {
const [expanded, setExpanded] = useState(false) // На верхнем уровне
const [details, setDetails] = useState(null)
const [selectedIds, setSelectedIds] = useState<string[]>([])
return (
<div>
{users.map(user => (
<UserItem
key={user.id}
user={user}
expanded={expanded}
details={details}
isSelected={selectedIds.includes(user.id)}
onToggleSelect={() => {
setSelectedIds(prev =>
prev.includes(user.id)
? prev.filter(id => id !== user.id)
: [...prev, user.id]
)
}}
/>
))}
</div>
)
}
function UserItem({ user, isSelected, onToggleSelect }) {
return (
<div>
<input
type="checkbox"
checked={isSelected}
onChange={onToggleSelect}
/>
{user.name}
</div>
)
}
Исключение: Кастомные hooks
✅ ПРАВИЛЬНО: hooks можно использовать в кастомных хуках
// Кастомный hook - это обычная функция, но начинается с "use"
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue) // OK!
const increment = () => setCount(prev => prev + 1)
const decrement = () => setCount(prev => prev - 1)
return { count, increment, decrement }
}
// Использование в компоненте
function MyComponent() {
const { count, increment } = useCounter(0) // OK!
return (
<div>
<p>{count}</p>
<button onClick={increment}>+</button>
</div>
)
}
ESLint правила
Для защиты от ошибок используй ESLint плагин:
npm install --save-dev eslint-plugin-react-hooks
// .eslintrc.json
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
Это предупредит о нарушениях правил hooks.
Вывод
Hooks можно использовать ТОЛЬКО:
- На верхнем уровне React компонента
- На верхнем уровне кастомного hook
- Вне условий, циклов и вложенных функций
Hooks НЕЛЬЗЯ использовать:
- В обычных функциях
- В условиях (if/else)
- В циклах (for/while)
- В обработчиках событий
- В классовых компонентах
- Внутри try/catch блоков
Эти правила существуют потому, что React полагается на порядок вызовов hooks для правильного управления состоянием. Нарушение порядка приводит к ошибкам.