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

Как кеширование влияет на дочерний компонент?

1.7 Middle🔥 191 комментариев
#React

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

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

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

Влияние кеширования на дочерние компоненты в React

Кеширование в React — это техника оптимизации производительности, которая предотвращает ненужные перерисовки компонентов. В React существует несколько механизмов кеширования (мемоизация), которые существенно влияют на поведение дочерних компонентов: React.memo, useMemo, useCallback. Разберу, как они работают и как влияют на производительность.

Проблема: ненужные перерисовки дочерних компонентов

Когда родительский компонент перерисовывается, все его дочерние компоненты также перерисовываются по умолчанию, даже если их props не изменились:

// Родитель
function ParentComponent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent name="John" />
    </div>
  );
}

// Дочерний компонент
function ChildComponent({ name }) {
  console.log('ChildComponent rendered'); // Логируется при КАЖДОМ клике на кнопку
  return <p>Hello, {name}</p>;
}

Проблема: ChildComponent перерисовывается каждый раз, когда изменяется count, хотя его prop name остаётся тем же.

Решение 1: React.memo — кеширование рендеринга компонента

React.memo — это высокоуровневый компонент (HOC), который мемоизирует результат рендеринга компонента и пересчитывает его только если props изменились:

// Дочерний компонент с мемоизацией
const ChildComponent = React.memo(function ChildComponent({ name }) {
  console.log('ChildComponent rendered');
  return <p>Hello, {name}</p>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent name="John" />
    </div>
  );
}
// ChildComponent рендерится только один раз, потом кешируется

Проблема с объектами props:

function ParentComponent() {
  const [count, setCount] = useState(0);

  // Проблема: новый объект создаётся при каждом рендере
  const user = { name: 'John', age: 30 };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent user={user} />
    </div>
  );
}

// ChildComponent будет перерисован, потому что user — новый объект при каждом рендере
const ChildComponent = React.memo(({ user }) => {
  console.log('Child rendered');
  return <p>{user.name}</p>;
});

Решение: использовать useMemo для стабилизации объектов:

function ParentComponent() {
  const [count, setCount] = useState(0);

  // Кешируем объект — пересчитывается только если user изменяется
  const user = useMemo(() => ({ name: 'John', age: 30 }), []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent user={user} />
    </div>
  );
}

const ChildComponent = React.memo(({ user }) => {
  console.log('Child rendered'); // Логируется только один раз
  return <p>{user.name}</p>;
});

Решение 2: useMemo — кеширование дорогих вычислений

useMemo кеширует результат функции и пересчитывает его только при изменении зависимостей:

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState([1, 2, 3, 4, 5]);

  // Кешируем дорогое вычисление
  const expensiveSum = useMemo(() => {
    console.log('Computing sum...');
    return items.reduce((a, b) => a + b, 0);
  }, [items]); // Пересчитываем только если items изменяется

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <p>Sum: {expensiveSum}</p>
      <ChildComponent sum={expensiveSum} />
    </div>
  );
}

const ChildComponent = React.memo(({ sum }) => {
  console.log('Child rendered');
  return <p>Sum from parent: {sum}</p>;
});

Решение 3: useCallback — кеширование функций обратного вызова

useCallback кеширует функцию и возвращает ту же ссылку, пока не изменятся зависимости. Это важно для дочерних компонентов, которые получают функции в props:

function ParentComponent() {
  const [count, setCount] = useState(0);

  // Без useCallback: новая функция при каждом рендере
  const handleClick = () => {
    console.log('Button clicked');
  };

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent onClickButton={handleClick} />
    </div>
  );
}

const ChildComponent = React.memo(({ onClickButton }) => {
  console.log('Child rendered'); // Логируется при КАЖДОМ рендере родителя
  return <button onClick={onClickButton}>Click me</button>;
});

С useCallback:

function ParentComponent() {
  const [count, setCount] = useState(0);

  // Кешируем функцию — возвращаем ту же ссылку, пока зависимости не изменятся
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent onClickButton={handleClick} />
    </div>
  );
}

const ChildComponent = React.memo(({ onClickButton }) => {
  console.log('Child rendered'); // Логируется только один раз
  return <button onClick={onClickButton}>Click me</button>;
});

Практический пример: список товаров

function ProductList() {
  const [selectedCategory, setSelectedCategory] = useState('electronics');
  const [sortOrder, setSortOrder] = useState('asc');

  // Кешируем отфильтрованный и отсортированный список
  const products = useMemo(() => {
    console.log('Computing filtered products...');
    let filtered = mockProducts.filter(p => p.category === selectedCategory);
    return sortOrder === 'asc' 
      ? filtered.sort((a, b) => a.price - b.price)
      : filtered.sort((a, b) => b.price - a.price);
  }, [selectedCategory, sortOrder]);

  // Кешируем callback для удаления товара
  const handleDeleteProduct = useCallback((id) => {
    // Удаляем товар
  }, []);

  return (
    <div>
      <select onChange={(e) => setSelectedCategory(e.target.value)}>
        <option>electronics</option>
        <option>books</option>
      </select>
      
      <select onChange={(e) => setSortOrder(e.target.value)}>
        <option value="asc">Price: Low to High</option>
        <option value="desc">Price: High to Low</option>
      </select>

      <ProductGrid 
        products={products} 
        onDeleteProduct={handleDeleteProduct} 
      />
    </div>
  );
}

const ProductGrid = React.memo(({ products, onDeleteProduct }) => {
  console.log('ProductGrid rendered');
  return (
    <div>
      {products.map(product => (
        <ProductCard 
          key={product.id} 
          product={product}
          onDelete={onDeleteProduct}
        />
      ))}
    </div>
  );
});

const ProductCard = React.memo(({ product, onDelete }) => {
  console.log('ProductCard rendered:', product.id);
  return (
    <div>
      <h3>{product.name}</h3>
      <p>${product.price}</p>
      <button onClick={() => onDelete(product.id)}>Delete</button>
    </div>
  );
});

Когда использовать кеширование

Используй React.memo если:

  • Компонент часто перерисовывается, а props редко меняются
  • Компонент дорогой для рендеринга (сложные вычисления, большой DOM)
const ExpensiveComponent = React.memo(({ data }) => {
  // Сложные вычисления
  return <ComplexVisualization data={data} />;
});

Используй useMemo если:

  • Вычисления дорогие (фильтрация, сортировка больших списков)
  • Результат передаётся в дочерние компоненты через props

Используй useCallback если:

  • Функция передаётся в дочерний компонент, завёрнутый в React.memo
  • Функция используется как зависимость в других хуках

Антипаттерны

Избегай излишнего кеширования:

// Плохо: кешируем примитив (не имеет смысла)
const count = useMemo(() => 5, []);

// Хорошо: примитивы уже кешированы JavaScript
const count = 5;

Не забывай про зависимости:

// Плохо: зависимости забыты, стоит использовать старое значение
const sum = useMemo(() => items.reduce((a, b) => a + b, 0), []);

// Хорошо: указываем зависимости
const sum = useMemo(() => items.reduce((a, b) => a + b, 0), [items]);

Измерение производительности

function ParentComponent() {
  console.time('ParentComponent');
  const [count, setCount] = useState(0);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ChildComponent />
      {console.timeEnd('ParentComponent')}
    </div>
  );
}

Выводы

  • React.memo предотвращает перерисовку компонента, если props не изменились
  • useMemo кеширует результат дорогих вычислений
  • useCallback кеширует функции для использования в дочерних компонентах
  • Правильное кеширование улучшает производительность, неправильное — усложняет код
  • Используй DevTools Profiler для измерения реальной производительности
  • Не кешируй без необходимости — помни о принципе KISS
Как кеширование влияет на дочерний компонент? | PrepBro