Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего передавать функцию в useState?
Передача функции в useState называется lazy initialization. Это оптимизация производительности, которая откладывает вычисление начального состояния до первого рендера компонента.
Проблема: дорогие вычисления
Плохо: синхронное вычисление
function MyComponent() {
// Это выполняется при КАЖДОМ рендере!
const initialState = expensiveCalculation();
const [count, setCount] = useState(initialState);
return <div>{count}</div>;
}
// expensiveCalculation может быть:
function expensiveCalculation() {
console.log('Computing...'); // Будет видно при каждом рендере
// Тяжелые вычисления
let result = 0;
for (let i = 0; i < 1000000000; i++) {
result += Math.random();
}
return result;
}
Проблема: expensiveCalculation() вызывается при каждом рендере компонента, хотя начальное состояние нужно только один раз!
Хорошо: lazy initialization с функцией
function MyComponent() {
// Функция вызовется только один раз при монтировании компонента
const [count, setCount] = useState(() => expensiveCalculation());
return <div>{count}</div>;
}
Решение: React вызовет функцию только один раз, при первом рендере. При последующих рендерах функция игнорируется.
Как это работает
Обычный useState (с прямым значением)
// Вариант 1: Прямое значение
const [state, setState] = useState(0);
// React использует значение как есть
useState с функцией (lazy initialization)
// Вариант 2: Функция-инициализатор
const [state, setState] = useState(() => {
// Эта функция вызывается ТОЛЬКО при монтировании
const initialValue = expensiveCalculation();
return initialValue;
});
Практические примеры
1. Инициализация из localStorage
function useLocalStorage(key: string, defaultValue: any) {
// Плохо - читает из localStorage при каждом рендере
// const [value, setValue] = useState(JSON.parse(localStorage.getItem(key)));
// Хорошо - читает только один раз
const [value, setValue] = useState(() => {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
});
const setStoredValue = (newValue: any) => {
localStorage.setItem(key, JSON.stringify(newValue));
setValue(newValue);
};
return [value, setStoredValue];
}
// Использование
function MyComponent() {
const [userSettings, setUserSettings] = useLocalStorage('settings', {});
return (
<div>
<p>Loaded once on mount: {userSettings.theme}</p>
</div>
);
}
2. Вычисление на основе props
interface ListProps {
items: string[];
}
function List({ items }: ListProps) {
// Плохо - сортирует при каждом рендере
// const [sorted, setSorted] = useState(items.sort());
// Хорошо - сортирует только один раз
const [sorted, setSorted] = useState(() => {
console.log('Sorting items...');
return [...items].sort();
});
return (
<ul>
{sorted.map(item => <li key={item}>{item}</li>)}
</ul>
);
}
3. Инициализация сложного объекта
interface User {
id: number;
name: string;
email: string;
settings: Record<string, any>;
}
function UserProfile({ userId }: { userId: number }) {
// Плохо
// const [user, setUser] = useState({
// id: userId,
// name: '',
// email: '',
// settings: { theme: 'dark', notifications: true }
// });
// Хорошо
const [user, setUser] = useState<User>(() => ({
id: userId,
name: '',
email: '',
settings: { theme: 'dark', notifications: true }
}));
return <div>{user.name}</div>;
}
4. API запрос для инициализации
function UserList() {
// Плохо - вызовет API при каждом рендере (вне useEffect)
// const [users, setUsers] = useState(fetchUsersSync());
// Хорошо с useEffect (стандартный способ)
const [users, setUsers] = useState<User[]>([]);
useEffect(() => {
fetchUsers().then(setUsers);
}, []);
// Или с lazy initialization (если fetchUsers синхронна)
const [cachedUsers, setCachedUsers] = useState(() => {
return getUsersFromCache(); // Синхронная операция
});
return <div>{users.length} users</div>;
}
Сравнение: с функцией vs без
function Performance() {
// Вариант 1: БЕЗ функции - медленно
const start1 = Date.now();
const [count1, setCount1] = useState(() => {
console.log('Initializing state 1...');
let sum = 0;
for (let i = 0; i < 1000000000; i++) {
sum += i;
}
console.log('Time:', Date.now() - start1); // ~500ms
return 0;
});
// Вариант 2: С функцией - быстро при повторных рендерах
// При re-render эта функция НЕ вызывается!
return (
<button onClick={() => setCount1(count1 + 1)}>
Count: {count1}
</button>
);
}
Когда использовать lazy initialization
Используй функцию когда:
-
Дорогие вычисления
useState(() => expensiveComputation()) -
Читаешь из localStorage/sessionStorage
useState(() => JSON.parse(localStorage.getItem('key'))) -
Парсишь JSON
useState(() => JSON.parse(jsonString)) -
Создаешь новый объект/массив
useState(() => ({ count: 0 })) -
Зависит от props, который не меняется
useState(() => parseQuery(location.search))
Не нужна функция когда:
-
Простое значение
useState(0) useState('default') -
Значение из props
// Если props меняется, используй useEffect const [value, setValue] = useState(props.initialValue); useEffect(() => { setValue(props.initialValue); // Обновить если props изменился }, [props.initialValue]); -
Асинхронная операция
// Используй useEffect, не useState const [data, setData] = useState(null); useEffect(() => { fetchData().then(setData); }, []);
Типичные ошибки
Ошибка 1: Забыть return в функции
// Неправильно
const [count, setCount] = useState(() => {
let value = 0;
for (let i = 0; i < 100; i++) {
value += i;
}
// Забыли return!
});
// count будет undefined!
// Правильно
const [count, setCount] = useState(() => {
let value = 0;
for (let i = 0; i < 100; i++) {
value += i;
}
return value; // return!
});
Ошибка 2: Вызвать функцию вместо передачи
// Неправильно - вызывает функцию сразу!
const [count, setCount] = useState(expensiveCalculation());
// Правильно - передает функцию
const [count, setCount] = useState(() => expensiveCalculation());
Ошибка 3: Использовать async функцию
// Неправильно - async функция возвращает Promise
const [data, setData] = useState(async () => {
const response = await fetch('/api/data');
return response.json();
});
// data будет Promise!
// Правильно - используй useEffect
const [data, setData] = useState(null);
useEffect(() => {
(async () => {
const response = await fetch('/api/data');
const json = await response.json();
setData(json);
})();
}, []);
React DevTools совет
Вы можете видеть, сколько раз вызывается функция инициализации:
const [count, setCount] = useState(() => {
console.log('Initializing...'); // Будет выведено один раз при монтировании
return 0;
});
// Нажмешь кнопку, re-render произойдет, но console.log НЕ появится
return <button onClick={() => setCount(count + 1)}>+</button>;
Вывод
Передавай функцию в useState когда:
- Инициализация требует дорогих вычислений
- Нужно прочитать из localStorage/sessionStorage
- Нужно создать новый объект/массив
- Инициализация зависит от сложной логики
Преимущества:
- Функция вызывается только один раз при монтировании
- При re-render функция игнорируется
- Улучшает производительность компонента
- Проясняет намерение: "это инициализация"
Помни: React вызовет функцию только один раз, поэтому дорогие вычисления не повлияют на производительность re-render.
// Правильный паттерн
const [state, setState] = useState(() => {
// Выполняется один раз при монтировании
const initialValue = /* дорогие вычисления или IO операции */;
return initialValue;
});