Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Может ли useState принять callback?
Да, useState может принять функцию-инициализатор (initializer function), которая будет вызвана при первом рендере компонента. Это часто путают с callback, но это не совсем одно и то же.
Основное использование
Обычно useState принимает начальное значение:
const [count, setCount] = useState(0); // начальное значение = 0
const [user, setUser] = useState(null); // начальное значение = null
Функция-инициализатор (Initializer Function)
Если передать функцию вместо значения, React вызовет её только один раз при монтировании компонента:
// ✅ Правильно: функция как инициализатор
const [count, setCount] = useState(() => {
console.log("Инициализация состояния");
return 5; // Это начальное значение
});
// ❌ Неправильно: функция как значение
const [count, setCount] = useState(() => {
return 5;
}); // React будет хранить саму функцию, не результат её выполнения!
Когда использовать?
Функция-инициализатор полезна в нескольких случаях:
1. Дорогостоящие вычисления
Без инициализатора вычисления происходят на каждый рендер:
// ❌ Плохо: функция создаётся на каждый рендер
function UserList() {
const [users, setUsers] = useState(
getAllUsersFromDatabase() // Вызывается на КАЖДЫЙ рендер!
);
// ...
}
// ✅ Хорошо: функция вызывается только один раз
function UserList() {
const [users, setUsers] = useState(() => {
return getAllUsersFromDatabase(); // Вызывается только при монтировании
});
// ...
}
2. Инициализация из localStorage
// ✅ Правильный паттерн
function Counter() {
const [count, setCount] = useState(() => {
const saved = localStorage.getItem("count");
return saved ? JSON.parse(saved) : 0;
});
useEffect(() => {
localStorage.setItem("count", JSON.stringify(count));
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
3. Сложная логика инициализации
function ComplexComponent() {
const [config, setConfig] = useState(() => {
// Сложная логика
const baseConfig = getDefaultConfig();
const userPreferences = loadUserPreferences();
const merged = { ...baseConfig, ...userPreferences };
if (merged.theme === "auto") {
merged.theme = prefersDark() ? "dark" : "light";
}
return merged;
});
return <div>{/* ... */}</div>;
}
4. Инициализация из Props (с осторожностью)
// ✅ Правильно
function Modal({ initialValue }) {
const [value, setValue] = useState(() => {
return processInitialValue(initialValue);
});
return <input value={value} onChange={(e) => setValue(e.target.value)} />;
}
Callback как обновление состояния?
В setCount() можно передать callback второго аргумента, который выполнится после обновления состояния (как в классовых компонентах):
// ❌ Старый способ (классовые компоненты)
this.setState({ count: 5 }, () => {
console.log("Состояние обновлено");
});
// ✅ Новый способ (хуки) — используй useEffect!
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Состояние обновлено:", count);
}, [count]);
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
Важное уточнение: React 19+
В React 19 добавлена поддержка transition callbacks через useActionState, но в стандартном useState callback всё ещё нужно реализовать через useEffect.
Частая ошибка: путаница с функциями
// ❌ ОШИБКА: функция сохранится как значение!
const [action, setAction] = useState(() => {
return () => console.log("Привет");
});
// Затем:
action(); // Не сработает как ожидается
// ✅ ПРАВИЛЬНО:
const [message, setMessage] = useState(() => {
return "Привет"; // Инициализатор возвращает значение, не функцию
});
// Для функции:
const [handler, setHandler] = useState(() => () => console.log("Привет"));
handler(); // Вызовет функцию
Оптимизация с инициализатором
// Хук для инициализации из API
function useInitialUserData(userId) {
const [user, setUser] = useState(() => {
// Синхронно загружаем из кэша, если есть
const cached = userCache.get(userId);
return cached || null;
});
useEffect(() => {
// Потом загружаем с сервера асинхронно
if (!user) {
fetchUser(userId).then(setUser);
}
}, [userId]);
return user;
}
Практический пример: сложная инициализация формы
function UserForm({ userId }) {
const [formData, setFormData] = useState(() => {
const user = getUserFromCache(userId);
if (user) {
return {
name: user.name,
email: user.email,
role: user.role || "user",
isActive: true,
};
}
return {
name: "",
email: "",
role: "user",
isActive: true,
};
});
const handleChange = (field, value) => {
setFormData(prev => ({
...prev,
[field]: value,
}));
};
return (
<form>
<input
value={formData.name}
onChange={(e) => handleChange("name", e.target.value)}
/>
<input
value={formData.email}
onChange={(e) => handleChange("email", e.target.value)}
/>
<select
value={formData.role}
onChange={(e) => handleChange("role", e.target.value)}
>
<option>user</option>
<option>admin</option>
</select>
</form>
);
}
Заключение
- useState может принять функцию-инициализатор — она выполнится один раз при монтировании
- Используй инициализатор для дорогостоящих вычислений, работы с localStorage, и сложной логики инициализации
- Для callback после обновления используй
useEffect, не пытайся передать второй аргумент вsetCount() - Избегай путаницы: инициализатор возвращает начальное значение, не функцию