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

Какие Hooks позволяют оптимизировать React?

1.6 Junior🔥 111 комментариев
#Фреймворки и библиотеки

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

🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)

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

React Hooks для оптимизации производительности

React hooks — это мощный инструмент для оптимизации компонентов. Хотя я backend разработчик, работаю с React в проектах и знаю, какие hooks помогают избежать ненужных рендеров и улучшить производительность.

1. useMemo — Мемоизация вычислений

Проблема: Дорогие вычисления при каждом рендере

// ❌ Плохо: calculateExpensiveValue вызывается при КАЖДОМ рендере
function UserProfile({ users }) {
  const expensiveValue = calculateExpensiveValue(users);
  
  return <div>{expensiveValue}</div>;
}

// При 10 000 пользователях это может занять 500ms на каждый рендер!

Решение: useMemo

import { useMemo } from 'react';

function UserProfile({ users }) {
  const expensiveValue = useMemo(() => {
    console.log('Calculating...');
    return calculateExpensiveValue(users);
  }, [users]);  // Пересчитывается только если users изменилась
  
  return <div>{expensiveValue}</div>;
}

// Log будет выведен только когда users изменится
// Не при каждом рендере!

Когда использовать useMemo:

  • Дорогие вычисления (сортировка, фильтрация больших массивов)
  • Сложные трансформации данных
  • Вычисления требующие O(n²) или больше
const sortedUsers = useMemo(() => {
  // O(n log n) — дорого
  return users.sort((a, b) => a.name.localeCompare(b.name));
}, [users]);

const filteredAndGrouped = useMemo(() => {
  // Сложная фильтрация
  return users
    .filter(u => u.active)
    .reduce((acc, u) => {
      acc[u.department] = [...(acc[u.department] || []), u];
      return acc;
    }, {});
}, [users]);

2. useCallback — Мемоизация функций

Проблема: Новая функция при каждом рендере

// ❌ Плохо: handleClick создается заново при каждом рендере
function Button() {
  const handleClick = () => {
    console.log('Clicked');
  };
  
  return <button onClick={handleClick}>Click me</button>;
}

// Если Button имеет дочерние компоненты со своей оптимизацией
// они будут ре-рендериться из-за новой функции

Решение: useCallback

import { useCallback } from 'react';

function Button() {
  const handleClick = useCallback(() => {
    console.log('Clicked');
  }, []);  // Функция никогда не пересоздается
  
  return <button onClick={handleClick}>Click me</button>;
}

// Или с зависимостями
function UserForm({ userId }) {
  const handleSubmit = useCallback(async (data) => {
    await api.updateUser(userId, data);
  }, [userId]);  // Пересоздается только если userId изменится
  
  return <form onSubmit={handleSubmit}>...</form>;
}

Когда использовать useCallback:

  • Передача callback в optimized child компоненты (React.memo)
  • Использование функции в зависимостях других hooks
  • Event handlers, которые передаются children
const List = React.memo(function List({ items, onItemClick }) {
  return items.map(item => (
    <Item key={item.id} item={item} onClick={onItemClick} />
  ));
});

function Parent() {
  const handleItemClick = useCallback((id) => {
    console.log('Clicked:', id);
  }, []);
  
  return <List items={items} onItemClick={handleItemClick} />;
}
// Без useCallback List пересчитывается на каждый рендер Parent!

3. React.memo — Мемоизация компонентов

Проблема: Компонент рендерится даже если props не изменились

// ❌ Плохо: UserCard рендерится при каждом рендере Parent
function UserCard({ user }) {
  console.log('UserCard render');
  return <div>{user.name}</div>;
}

function Parent({ users }) {
  return users.map(user => <UserCard key={user.id} user={user} />);
}

// Если в Parent есть другой state — все UserCard ре-рендерятся!

Решение: React.memo

// ✅ Хорошо: рендерится только если user prop изменился
const UserCard = React.memo(function UserCard({ user }) {
  console.log('UserCard render');
  return <div>{user.name}</div>;
});

function Parent({ users }) {
  return users.map(user => <UserCard key={user.id} user={user} />);
}

// Теперь UserCard#1 не ре-рендерится если изменился UserCard#2!

Custom comparison:

const UserCard = React.memo(
  function UserCard({ user, onUpdate }) {
    return <div onClick={() => onUpdate(user.id)}>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // Вернуть true если props "равны" (не ре-рендерить)
    return prevProps.user.id === nextProps.user.id &&
           prevProps.onUpdate === nextProps.onUpdate;
  }
);

4. useReducer — Оптимизированное state management

Проблема: useState с множеством состояний

// ❌ Плохо: множество useState
function Form() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [phone, setPhone] = useState('');
  const [address, setAddress] = useState('');
  // ... 10 more fields
  
  // Каждое изменение вызывает ре-рендер всего компонента
}

Решение: useReducer

const initialState = {
  name: '',
  email: '',
  phone: '',
  address: ''
};

function formReducer(state, action) {
  switch (action.type) {
    case 'UPDATE_FIELD':
      return { ...state, [action.field]: action.value };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
}

function Form() {
  const [state, dispatch] = useReducer(formReducer, initialState);
  
  const handleChange = (field) => (e) => {
    dispatch({ type: 'UPDATE_FIELD', field, value: e.target.value });
  };
  
  return (
    <form>
      <input value={state.name} onChange={handleChange('name')} />
      <input value={state.email} onChange={handleChange('email')} />
    </form>
  );
}

Преимущества:

  • Одна функция dispatch вместо множества setState
  • Логика state управления в одном месте
  • Легче тестировать (reducer — pure function)

5. useContext — Избегание prop drilling

Проблема: Prop drilling (пробрасывание пропсов через множество компонентов)

// ❌ Плохо: theme пробрасывается через все компоненты
function App() {
  const [theme, setTheme] = useState('light');
  return <Navbar theme={theme} setTheme={setTheme} />;
}

function Navbar({ theme, setTheme }) {
  return <Menu theme={theme} setTheme={setTheme} />;
}

function Menu({ theme, setTheme }) {
  return <Button theme={theme} onClick={() => setTheme('dark')} />;
}

Решение: useContext

const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Navbar />
    </ThemeContext.Provider>
  );
}

function Button() {
  const { theme, setTheme } = useContext(ThemeContext);
  return <button onClick={() => setTheme('dark')}>{theme}</button>;
}

// Navbar и Menu не нужно знать про theme!

Оптимизация useContext:

// ✅ Разделить контексты по изменяемости
const ThemeContext = createContext();     // Редко меняется
const UserContext = createContext();      // Часто меняется

// Иначе все компоненты ре-рендерятся при изменении

6. useEffect с зависимостями — Контроль side effects

Оптимизация:

// ❌ Плохо: выполняется при каждом рендере
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    api.getUser(userId).then(setUser);
    // Без зависимостей — бесконечные запросы!
  });
  
  return <div>{user?.name}</div>;
}

// ✅ Хорошо: выполняется только когда userId изменится
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    api.getUser(userId).then(setUser);
  }, [userId]);  // Зависимость!
  
  return <div>{user?.name}</div>;
}

7. useTransition — Оптимизация медленных обновлений

Проблема: Медленный поиск блокирует UI

// ❌ Плохо: input lag при поиске в большом списке
function SearchUsers({ users }) {
  const [search, setSearch] = useState('');
  const filtered = users.filter(u => 
    u.name.toLowerCase().includes(search.toLowerCase())
  );
  
  return (
    <>
      <input 
        value={search}
        onChange={e => setSearch(e.target.value)}
        // Это блокирует UI на 100ms+!
      />
      {filtered.map(u => <div key={u.id}>{u.name}</div>)}
    </>
  );
}

Решение: useTransition

import { useTransition } from 'react';

function SearchUsers({ users }) {
  const [search, setSearch] = useState('');
  const [isPending, startTransition] = useTransition();
  
  const filtered = users.filter(u => 
    u.name.toLowerCase().includes(search.toLowerCase())
  );
  
  return (
    <>
      <input 
        value={search}
        onChange={e => {
          setSearch(e.target.value);
          startTransition(() => {
            // Это обновление менее приоритетно
            // UI остается отзывчивым
          });
        }}
      />
      {isPending && <p>Searching...</p>}
      {filtered.map(u => <div key={u.id}>{u.name}</div>)}
    </>
  );
}

8. useDeferredValue — Отложенное обновление значения

function App() {
  const [input, setInput] = useState('');
  const deferredInput = useDeferredValue(input);
  
  return (
    <>
      <input value={input} onChange={e => setInput(e.target.value)} />
      <ExpensiveList query={deferredInput} />
    </>
  );
}

// input обновляется сразу (UI responsive)
// ExpensiveList обновляется с задержкой (менее приоритетно)

Правило: Когда оптимизировать

// Профилируй ДО оптимизации!
function App() {
  return <Profiler id="app" onRender={onRender}>
    {/* Компоненты */}
  </Profiler>;
}

const onRender = (
  id,         // Идентификатор профайлера
  phase,      // "mount" или "update"
  actualDuration,  // Время рендера
  baseDuration,    // Время без мемоизации
  startTime,  // Время начала
  commitTime  // Время завершения
) => {
  console.log(`${id} took ${actualDuration}ms`);
};

Best Practices

  1. Не оптимизируй без данных — используй React DevTools Profiler
  2. Не überoptimize — memoization сама по себе имеет стоимость
  3. Правильно настраивай зависимости — неправильные зависимости опаснее отсутствия мемоизации
  4. Используй key в lists — это базовая оптимизация
  5. Разделяй state — разные части state менять отдельно

Оптимизация React — это баланс между производительностью и сложностью кода. Начни с простого, профилируй, потом оптимизируй.