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

Может ли useState принять callback?

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

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

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

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

Может ли 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()
  • Избегай путаницы: инициализатор возвращает начальное значение, не функцию