JavaRush /Курсы /Модуль 3: React /Типизация функций в `useCallback` и передача зависимостей...

Типизация функций в `useCallback` и передача зависимостей

Модуль 3: React
3 уровень , 4 лекция
Открыта

Зачем типизировать функции в useCallback

Мотивация здесь простая: с типами легче жить. Без грамотно описанных типов в масштабных приложениях вы рискуете столкнуться с огромным количеством багов, связанных с несовместимостью данных. TypeScript позволяет нам не только избежать большинства этих ошибок, но и наглядно описывать, что мы ожидаем на вход и хотим получить на выход.

useCallback принимает две вещи: колбэк-функцию и массив зависимостей. Колбэк — это ваш драгоценный кусочек логики, который должен быть правильно типизирован. Но вот беда: если забыть указать типы, можно пропустить тот момент, когда передали совершенно не то, что нужно. Начинаем разбираться, как этого избежать.

Типизация функций в useCallback

Основной синтаксис

Для начала посмотрим, как типизировать простейшую функцию в useCallback:

import React, { useCallback } from "react";

// Интерфейс для пропсов компонента
interface MyButtonProps {
  onClick: (id: number) => void; // Функция принимает число и ничего не возвращает
}

// Компонент, который принимает обработчик клика
const MyButton: React.FC<MyButtonProps> = ({ onClick }) => {
  return <button onClick={() => onClick(42)}>Click me</button>;
};

// Родительский компонент
const App: React.FC = () => {
  // Функция с мемоизацией
  const handleClick = useCallback((id: number): void => {
    console.log(`Button clicked with ID: ${id}`);
  }, []); // Зависимости указываются вот здесь — мы их обсудим чуть позже

  return <MyButton onClick={handleClick} />;
};

export default App;

В данном примере мы:

  1. Определили интерфейс пропсов для компонента MyButton, где чётко указали, что onClick — это функция, принимающая id: number и ничего не возвращающая void.
  2. В App типизировали функцию в useCallback аналогично, чтобы всё было согласовано.

А если возвращаемое значение не void?

Давайте чуть усложним: предположим, что функция должна возвращать строку.

const useFormattedMessage = (): ((name: string) => string) => {
  return useCallback((name: string): string => {
    return `Hello, ${name}!`;
  }, []);
};

const App: React.FC = () => {
  const getMessage = useFormattedMessage();

  console.log(getMessage("Alice")); // Hello, Alice!

  return null;
};

Вот так просто: если вы возвращаете что-то полезное, тип возвращаемого значения всегда указывается после двоеточия как часть определения функции (: string в данном случае).

Типизация через интерфейсы

Иногда лучше вынести типы в интерфейсы, чтобы всё было более аккуратно (и пригодилось в других местах):

// Тип для функции
interface CalculateSum {
  (a: number, b: number): number;
}

const App: React.FC = () => {
  // Типизируем через интерфейс
  const calculateSum: CalculateSum = useCallback((a, b) => a + b, []);

  console.log(calculateSum(3, 4)); // 7

  return null;
};

Тип в интерфейсе задаёт "контракт", что удобнее, если такую функцию нужно передавать в разные места.

Управление зависимостями в useCallback

Что это за зависимости?

Если вы не совсем уверены, почему у useCallback есть массив зависимостей, попробуем просто: зависимости — это всё, что может измениться и повлиять на результат функции. React следит за тем, чтобы ваша мемоизированная функция обновлялась только при изменении перечисленных значений.

Пример:

const App: React.FC = () => {
  const [count, setCount] = React.useState(0);

  const increment = useCallback(() => {
    setCount(count + 1); // Заметьте, мы используем count внутри функции
  }, [count]); // Зависимость — count

  return <button onClick={increment}>Increment</button>;
};

Если мы не укажем count в зависимости, React будет держать "старую" версию функции, даже если счётчик обновится. Ваш пользователь нажмёт кнопку, а ничего не произойдёт — так-то!

Ошибки при работе с зависимостями

Если вы забыли указать зависимость, получите баг: мемоизированная функция будет работать с устаревшими значениями.

Пример ошибки:

// ОШИБКА: count не добавлен в зависимости
const increment = useCallback(() => {
  setCount(count + 1);
}, []); // Пустой массив зависимостей

React даже предупредит в консоли: "React Hook useCallback has a missing dependency".

Исправляем:

const increment = useCallback(() => {
  setCount((prev) => prev + 1); // Используем callback-версию setState
}, []); // Теперь зависимости не нужны!

Использование prev — один из способов обойти необходимость зависимостей, если вам неважно текущее состояние.

Перенасыщенные зависимости

Добавлять всё подряд в массив тоже плохо. Разберём пример:

const App: React.FC = () => {
  const [text, setText] = React.useState('');

  const updateText = useCallback(() => {
    console.log('Text changed');
  }, [text]); // НЕ НУЖНО указывать text, если updateText не использует его!

  return <input onChange={updateText} />;
};

Хотя updateText логически не зависит от text, ошибка программиста заставляет React пересоздавать функцию при каждом изменении text. Убираем эту зависимость.

Порядок управления зависимостями

Давайте соберём всё воедино:

  1. Указывайте только те зависимости, которые влияют на внутреннюю логику функции.
  2. Если функция зависит только от состояния, используйте callback-версию setState.
  3. Типизируйте массив зависимостей: если передаёте функции, они тоже должны быть соответствующим образом описаны.

Примеры типичных ошибок и их исправление

  1. Ошибка: использование неинициализированной переменной.
const App: React.FC = () => {
  const myVar = 42;
  const logVar = useCallback(() => {
    console.log(myVar);
  }, []);

  return <button onClick={logVar}>Log</button>;
};
// myVar не указан в зависимостях -> БАГ!

Исправление:

const logVar = useCallback(() => {
  console.log(myVar);
}, [myVar]);
  1. Ошибка: переопределённые значения вызывают постоянные обновления.
const App: React.FC = () => {
  const [count, setCount] = React.useState(0);

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return <button onClick={increment}>+</button>;
};
// React будет пересоздавать функцию при каждом рендере из-за count.

Исправление:

const increment = useCallback(() => {
  setCount((prev) => prev + 1); // Контролируем через prev
}, []);

На этом мы завершаем разбор типизации в useCallback и управления зависимостями. Не забудьте потренироваться типизировать и оптимизировать свои компоненты!

1
Задача
Модуль 3: React, 3 уровень, 4 лекция
Недоступна
Создание мемоизированной функции
Создание мемоизированной функции
1
Задача
Модуль 3: React, 3 уровень, 4 лекция
Недоступна
Передача мемоизированной функции с типизацией
Передача мемоизированной функции с типизацией
3
Опрос
Оптимизация компонентов, 3 уровень, 4 лекция
Недоступен
Оптимизация компонентов
Оптимизация компонентов
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ