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

Как в useState сохранить первоначальное состояние чтобы компонент не перерендовался?

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

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

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

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

Сохранение первоначального состояния в useState без перерендеров

Это частая задача при разработке на React - нужно сохранить исходное состояние и избежать ненужных перерендеров. Есть несколько подходов.

Проблема: обновление с теми же значениями вызывает перерендер

function UserProfile() {
  const [user, setUser] = useState({ name: 'John', age: 30 })
  const [initialUser, setInitialUser] = useState({ name: 'John', age: 30 })

  // Проблема: каждый раз создаются новые объекты
  // Даже если значения одинаковые, это разные объекты в памяти
  console.log('Рендер')
}

Браузер консоль выведет "Рендер" много раз.

Решение 1: Использование useRef для сохранения первоначального значения

useRef сохраняет значение между рендерами БЕЗ перерендера:

import { useState, useRef } from 'react'

function UserProfile() {
  const [user, setUser] = useState({ name: 'John', age: 30 })
  const initialUserRef = useRef({ name: 'John', age: 30 })

  const handleReset = () => {
    // Восстанавливаем исходное состояние
    setUser(initialUserRef.current)
  }

  return (
    <div>
      <button onClick={handleReset}>Сбросить на исходное</button>
    </div>
  )
}

Ключевое отличие:

  • useState вызывает перерендер при обновлении
  • useRef НЕ вызывает перерендер, просто хранит значение

Решение 2: Сохранение начального состояния через параметр

Используй функцию инициализации для избегания пересоздания:

function UserForm({ initialData }) {
  // Плохо: initialData будет пересоздаваться
  const [user, setUser] = useState(initialData)

  // Хорошо: инициализируется только один раз
  const [user, setUser] = useState(() => {
    // Эта функция вызывается только при первом рендере
    return { ...initialData }
  })

  const initialRef = useRef(initialData)
}

Решение 3: Сравнение объектов для избегания обновления

Проверяй, изменилось ли состояние, перед обновлением:

import { useState, useCallback } from 'react'

function UserForm() {
  const [user, setUser] = useState({ name: 'John', age: 30 })
  const [initialUser] = useState({ name: 'John', age: 30 })

  const handleChange = useCallback((newUser) => {
    // Только обновляй, если данные действительно изменились
    if (JSON.stringify(newUser) !== JSON.stringify(user)) {
      setUser(newUser)
    }
  }, [user])

  return <input onChange={e => handleChange({ ...user, name: e.target.value })} />
}

Или более эффективно с deep comparison:

function deepEqual(obj1, obj2) {
  return JSON.stringify(obj1) === JSON.stringify(obj2)
}

const handleChange = useCallback((newUser) => {
  if (!deepEqual(newUser, user)) {
    setUser(newUser)
  }
}, [user])

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

import { useState, useMemo } from 'react'

function UserForm({ userData }) {
  const [user, setUser] = useState(userData)
  
  // Сохраняем начальное состояние в memoized переменной
  const initialUser = useMemo(() => {
    return { ...userData }
  }, [userData])

  const handleReset = () => {
    setUser(initialUser)
  }

  return <button onClick={handleReset}>Сбросить</button>
}

Решение 5: Уменьшение состояния для избегания перерендеров

Вместо хранения всего объекта, храни только измененные значения:

function UserForm() {
  const [changes, setChanges] = useState({}) // Только изменения
  const initialUser = { name: 'John', age: 30 } // Константа, не состояние

  const currentUser = { ...initialUser, ...changes } // Слияние

  const handleChange = (field, value) => {
    setChanges(prev => ({ ...prev, [field]: value }))
  }

  const handleReset = () => {
    setChanges({}) // Просто очищаем изменения
  }

  return <button onClick={handleReset}>Сбросить</button>
}

Решение 6: useReducer для более сложного состояния

import { useReducer } from 'react'

const initialState = { name: 'John', age: 30 }

function reducer(state, action) {
  switch (action.type) {
    case 'CHANGE':
      return { ...state, [action.field]: action.value }
    case 'RESET':
      return initialState // Восстанавливаем из константы
    default:
      return state
  }
}

function UserForm() {
  const [user, dispatch] = useReducer(reducer, initialState)

  const handleReset = () => {
    dispatch({ type: 'RESET' })
  }

  return <button onClick={handleReset}>Сбросить</button>
}

Практический пример: форма редактирования

import { useState, useRef } from 'react'

function EditUser({ user: initialUser }) {
  const [user, setUser] = useState(initialUser)
  const savedUserRef = useRef(initialUser)

  const hasChanges = JSON.stringify(user) !== JSON.stringify(savedUserRef.current)

  const handleChange = (field, value) => {
    setUser(prev => ({ ...prev, [field]: value }))
  }

  const handleSave = async () => {
    await fetch(`/api/users/${user.id}`, {
      method: 'PUT',
      body: JSON.stringify(user)
    })
    savedUserRef.current = user // Обновляем сохраненное значение
  }

  const handleReset = () => {
    setUser(savedUserRef.current) // Восстанавливаем из сохраненного
  }

  return (
    <div>
      <input
        value={user.name}
        onChange={e => handleChange('name', e.target.value)}
      />
      <button onClick={handleSave} disabled={!hasChanges}>Сохранить</button>
      <button onClick={handleReset} disabled={!hasChanges}>Отменить</button>
    </div>
  )
}

Когда использовать какой подход

СценарийРешениеПричина
Сохранить исходное для resetuseRefНе вызовет перерендер
Инициализация сложнаяuseState(() => {})Функция вызывается только один раз
Избежать обновления если значение жеСравнение перед setStateЭкономит перерендер
Много полей в формеuseReducerБолее организованный код
Нужны только измененияХранить только changesМеньше данных в состоянии

Важная деталь: Object.freeze для защиты

const initialUser = Object.freeze({ name: 'John', age: 30 })

function UserForm() {
  const [user, setUser] = useState(initialUser)
  // initialUser больше не может быть случайно изменен
}

Резюме

Для сохранения первоначального состояния без перерендеров:

  1. Используй useRef если не нужен перерендер
  2. Используй мемоизированное значение если нужно отслеживать изменения
  3. Храни только изменения вместо всего состояния
  4. Используй useReducer для сложной логики
  5. Сравнивай значения перед setState если нужно экономить перерендеры

Право выбора зависит от того, нужен ли тебе перерендер при восстановлении исходного состояния или нет.

Как в useState сохранить первоначальное состояние чтобы компонент не перерендовался? | PrepBro