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

Как сохранить предыдущие Props компонента с помощью UseRef?

1.8 Middle🔥 61 комментариев
#React

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

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

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

Как сохранить предыдущие Props компонента с помощью useRef

useRef позволяет хранить значение, которое персистирует между рендерами, но не вызывает перерендер при изменении. Это идеально подходит для отслеживания предыдущих значений props.

Задача: Отследить изменения Props

Зачем это нужно?

  • Сравнить новые и старые значения
  • Выполнить действие только когда prop изменился
  • Отследить историю изменений

Способ 1: Сохранение одного prop

import { useRef, useEffect } from 'react';

function Counter({ count }) {
  const prevCountRef = useRef();
  
  useEffect(() => {
    // После рендера сохраняем текущее значение
    prevCountRef.current = count;
  }, [count]);
  
  return (
    <div>
      <p>Текущее значение: {count}</p>
      <p>Предыдущее значение: {prevCountRef.current}</p>
    </div>
  );
}

Как это работает:

  1. При первом рендере prevCountRef.current undefined
  2. После рендера useEffect сохраняет текущее значение count
  3. При изменении count компонент перерендеривается
  4. useEffect обновляет ref на новое значение

Способ 2: Создание пользовательского хука usePrevious

Удобный вспомогательный хук:

function usePrevious(value) {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  }, [value]);
  
  return ref.current;
}

function Component({ userId }) {
  const prevUserId = usePrevious(userId);
  
  return (
    <div>
      <p>Текущий ID: {userId}</p>
      <p>Предыдущий ID: {prevUserId}</p>
    </div>
  );
}

Способ 3: Сохранение нескольких props

import { useRef, useEffect } from 'react';

function User({ name, email, age }) {
  const prevPropsRef = useRef();
  
  useEffect(() => {
    // Сохраняем все предыдущие props
    prevPropsRef.current = { name, email, age };
  }, [name, email, age]);
  
  const prevProps = prevPropsRef.current;
  
  return (
    <div>
      <p>Имя: {name} (было {prevProps?.name})</p>
      <p>Email: {email} (было {prevProps?.email})</p>
      <p>Возраст: {age} (было {prevProps?.age})</p>
    </div>
  );
}

Способ 4: Обнаружение изменения конкретного prop

function UserProfile({ userId, userName, isActive }) {
  const prevUserIdRef = useRef();
  
  useEffect(() => {
    if (prevUserIdRef.current !== userId) {
      console.log('User ID изменился!');
      console.log(`Было: ${prevUserIdRef.current}, стало: ${userId}`);
    }
    prevUserIdRef.current = userId;
  }, [userId]);
  
  return (
    <div>
      <p>User ID: {userId}</p>
      <p>User Name: {userName}</p>
      <p>Active: {isActive ? 'Yes' : 'No'}</p>
    </div>
  );
}

Способ 5: Отслеживание предыдущих значений с логированием

function DataDisplay({ data, status }) {
  const prevDataRef = useRef();
  const prevStatusRef = useRef();
  
  useEffect(() => {
    const didDataChange = prevDataRef.current !== data;
    const didStatusChange = prevStatusRef.current !== status;
    
    if (didDataChange) {
      console.log('Данные обновились:', {
        prev: prevDataRef.current,
        current: data
      });
    }
    
    if (didStatusChange) {
      console.log('Статус изменился:', {
        prev: prevStatusRef.current,
        current: status
      });
    }
    
    prevDataRef.current = data;
    prevStatusRef.current = status;
  }, [data, status]);
  
  return (
    <div>
      <p>Data: {JSON.stringify(data)}</p>
      <p>Status: {status}</p>
    </div>
  );
}

Способ 6: Сравнение объектов в props

function UserCard({ user }) {
  const prevUserRef = useRef();
  
  useEffect(() => {
    if (JSON.stringify(prevUserRef.current) !== JSON.stringify(user)) {
      console.log('Пользователь изменился');
    }
    prevUserRef.current = user;
  }, [user]);
  
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

Способ 7: Отслеживание истории изменений

function ComponentWithHistory({ value }) {
  const historyRef = useRef([]);
  
  useEffect(() => {
    // Добавляем новое значение в историю
    historyRef.current.push({
      value,
      timestamp: new Date()
    });
    
    // Ограничиваем историю последними 10 изменениями
    if (historyRef.current.length > 10) {
      historyRef.current.shift();
    }
  }, [value]);
  
  const handleShowHistory = () => {
    console.log('История изменений:', historyRef.current);
  };
  
  return (
    <div>
      <p>Текущее значение: {value}</p>
      <button onClick={handleShowHistory}>Показать историю</button>
    </div>
  );
}

Способ 8: Сравнение глубоких изменений

function ComplexComponent({ data }) {
  const prevDataRef = useRef(data);
  
  useEffect(() => {
    const hasChanged = JSON.stringify(prevDataRef.current) !== JSON.stringify(data);
    
    if (hasChanged) {
      // Выполняем действие при изменении данных
      fetchMoreData(data);
    }
    
    prevDataRef.current = data;
  }, [data]);
  
  const fetchMoreData = (data) => {
    console.log('Получаем дополнительные данные для:', data);
  };
  
  return <div>{JSON.stringify(data)}</div>;
}

Способ 9: Использование ref для условного выполнения useEffect

function FormComponent({ formData, isSubmitted }) {
  const isFirstRenderRef = useRef(true);
  
  useEffect(() => {
    // Пропускаем первый рендер
    if (isFirstRenderRef.current) {
      isFirstRenderRef.current = false;
      return;
    }
    
    // Выполняем действие только при изменении после первого рендера
    console.log('Форма изменена (не первый рендер):', formData);
  }, [formData]);
  
  return (
    <div>
      <p>Заполненные данные: {JSON.stringify(formData)}</p>
    </div>
  );
}

Способ 10: Отслеживание с callback при изменении

function MonitoredComponent({ userId, onUserChange }) {
  const prevUserIdRef = useRef();
  
  useEffect(() => {
    if (prevUserIdRef.current && prevUserIdRef.current !== userId) {
      // Вызываем callback при изменении userId
      onUserChange({
        prevUserId: prevUserIdRef.current,
        newUserId: userId
      });
    }
    prevUserIdRef.current = userId;
  }, [userId, onUserChange]);
  
  return <div>User ID: {userId}</div>;
}

// Использование
<MonitoredComponent 
  userId={5} 
  onUserChange={(change) => console.log('Смена пользователя:', change)}
/>

Сравнение: useRef vs useState

useRef для предыдущего значения:

const prevCountRef = useRef();
useEffect(() => {
  prevCountRef.current = count;
}, [count]);
// Не вызывает перерендер

useState для предыдущего значения:

const [prevCount, setPrevCount] = useState();
useEffect(() => {
  setPrevCount(count);
}, [count]);
// Вызывает дополнительный перерендер

Практический пример: Отслеживание изменения user ID

function useUserData(userId) {
  const [userData, setUserData] = useState(null);
  const prevUserIdRef = useRef();
  
  useEffect(() => {
    // Выполняем запрос только если userId действительно изменился
    if (prevUserIdRef.current !== userId) {
      fetchUser(userId).then(setUserData);
      prevUserIdRef.current = userId;
    }
  }, [userId]);
  
  return userData;
}

function UserDetail({ userId }) {
  const userData = useUserData(userId);
  
  return (
    <div>
      {userData && (
        <>
          <h2>{userData.name}</h2>
          <p>{userData.email}</p>
        </>
      )}
    </div>
  );
}

Выводы

  • useRef сохраняет значение между рендерами без перерендера
  • Используй useEffect для обновления ref после каждого рендера
  • Создавай usePrevious хук для переиспользования логики
  • Сравнивай предыдущие и текущие значения для обнаружения изменений
  • useRef идеален для отслеживания истории и условного выполнения логики
  • Не забывай обновлять ref в useEffect, иначе он всегда будет содержать старое значение