← Назад к вопросам

Для чего передавать функцию в UseState?

2.0 Middle🔥 241 комментариев
#React

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего передавать функцию в 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

Используй функцию когда:

  1. Дорогие вычисления

    useState(() => expensiveComputation())
    
  2. Читаешь из localStorage/sessionStorage

    useState(() => JSON.parse(localStorage.getItem('key')))
    
  3. Парсишь JSON

    useState(() => JSON.parse(jsonString))
    
  4. Создаешь новый объект/массив

    useState(() => ({ count: 0 }))
    
  5. Зависит от props, который не меняется

    useState(() => parseQuery(location.search))
    

Не нужна функция когда:

  1. Простое значение

    useState(0)
    useState('default')
    
  2. Значение из props

    // Если props меняется, используй useEffect
    const [value, setValue] = useState(props.initialValue);
    
    useEffect(() => {
      setValue(props.initialValue); // Обновить если props изменился
    }, [props.initialValue]);
    
  3. Асинхронная операция

    // Используй 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;
});
Для чего передавать функцию в UseState? | PrepBro