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

В чем разница между componentDidCatch и getDerivedStateFromError?

1.6 Junior🔥 143 комментариев
#React

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

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

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

Error Boundaries: componentDidCatch vs getDerivedStateFromError

Это два метода в Error Boundary (class компоненты), которые отлавливают ошибки в React приложении. Они работают на разных этапах и имеют разные цели.

Краткое резюме

МетодЭтапДля чего
getDerivedStateFromErrorRenderОбновить state, чтобы отрисовать fallback UI
componentDidCatchПосле renderЛогирование, отправка в error tracking

getDerivedStateFromError: управление UI

Это статический метод, вызываемый во время render фазы:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  // Вызывается во время render (у компонента, который выбросил ошибку)
  static getDerivedStateFromError(error) {
    // СИНХРОННЫЙ метод
    // Обновляем state, чтобы отрисовать fallback UI
    return { hasError: true, error };
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h1>Упс, что-то пошло не так</h1>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false })}>
            Попробовать снова
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

Характеристики getDerivedStateFromError:

  • Статический метод (нет доступа к this)
  • Синхронный (блокирует render)
  • Вызывается во время render фазы
  • Используется для обновления state
  • Возвращает новый state, который будет merged
  • НЕЛЬЗЯ здесь логировать или делать побочные эффекты

componentDidCatch: логирование и побочные эффекты

Это нормальный метод, вызываемый ПОСЛЕ render и commit:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  // Вызывается после render (commit фаза)
  componentDidCatch(error, errorInfo) {
    // Асинхронный, побочные эффекты разрешены
    
    // 1. Логирование
    console.error('Error caught:', error, errorInfo);
    
    // 2. Отправка в error tracking (Sentry, LogRocket)
    Sentry.captureException(error, {
      contexts: {
        react: {
          componentStack: errorInfo.componentStack
        }
      }
    });
    
    // 3. Отправка на сервер
    fetch('/api/errors', {
      method: 'POST',
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        timestamp: new Date().toISOString()
      })
    });
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Error UI</div>;
    }
    return this.props.children;
  }
}

Характеристики componentDidCatch:

  • Обычный метод (имеет доступ к this)
  • Асинхронный (не блокирует render)
  • Вызывается ПОСЛЕ render и commit
  • Используется для логирования и побочных эффектов
  • Параметры: error и errorInfo
  • errorInfo.componentStack показывает полный стек ошибки

Практическая разница

// Полный пример Error Boundary
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null
    };
  }
  
  static getDerivedStateFromError(error) {
    // СИНХРОННО обновляем UI
    console.log('getDerivedStateFromError called (синхронно)');
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // АСИНХРОННО логируем
    console.log('componentDidCatch called (асинхронно)');
    
    this.setState({ errorInfo });
    
    // Отправляем на сервер (асинхронно)
    this.logErrorToService(error, errorInfo);
  }
  
  logErrorToService = async (error, errorInfo) => {
    try {
      await fetch('/api/errors', {
        method: 'POST',
        body: JSON.stringify({
          error: error.toString(),
          stack: errorInfo.componentStack
        })
      });
    } catch (e) {
      console.error('Failed to log error:', e);
    }
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h1>Кажется, произошла ошибка</h1>
          {process.env.NODE_ENV === 'development' && (
            <details style={{ whiteSpace: 'pre-wrap' }}>
              <summary>Детали ошибки</summary>
              <p>{this.state.error?.toString()}</p>
              <p>{this.state.errorInfo?.componentStack}</p>
            </details>
          )}
        </div>
      );
    }
    
    return this.props.children;
  }
}

Когда ошибка ловится

// Ловятся (Error Boundary поймает):
- Render методы
- Lifecycle методы (componentDidMount, etc.)
- Конструкторы
- setState callback

// НЕ ловятся (Event handlers, async code):
- Event handlers (нужно try-catch)
- setTimeout / async code
- Promise rejection
- SSR

Пример с Event Handler:

class MyComponent extends React.Component {
  handleClick = async () => {
    try {
      // Error Boundary НЕ поймает эту ошибку!
      throw new Error('Клик ошибка');
    } catch (error) {
      // Нужно self handle
      this.setState({ error });
    }
  }
  
  render() {
    return <button onClick={this.handleClick}>Клик</button>;
  }
}

Лучшие практики

1. Используй оба метода вместе

static getDerivedStateFromError(error) {
  return { hasError: true };
}

componentDidCatch(error, errorInfo) {
  // Логирование ПОСЛЕ того как UI обновлён
  logError(error, errorInfo);
}

2. Не логируй в getDerivedStateFromError

// ❌ НЕПРАВИЛЬНО
static getDerivedStateFromError(error) {
  console.log(error); // Может быть вызвано несколько раз
  Sentry.captureException(error); // В миддле render?
  return { hasError: true };
}

// ✅ ПРАВИЛЬНО
static getDerivedStateFromError(error) {
  return { hasError: true };
}

componentDidCatch(error, errorInfo) {
  console.log(error);
  Sentry.captureException(error);
}

3. Разные Error Boundaries для разных частей

function App() {
  return (
    <div>
      <ErrorBoundary>
        <Header />
      </ErrorBoundary>
      
      <ErrorBoundary>
        <MainContent />
      </ErrorBoundary>
      
      <ErrorBoundary>
        <Footer />
      </ErrorBoundary>
    </div>
  );
}
// Если Header упадёт, Footer и MainContent всё ещё работают

4. Используй Sentry или LogRocket

class ErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    // Профессиональное логирование
    Sentry.withScope(scope => {
      scope.setContext('react', {
        componentStack: errorInfo.componentStack
      });
      Sentry.captureException(error);
    });
  }
  
  render() {
    if (this.state.hasError) {
      return <div>Что-то пошло не так</div>;
    }
    return this.props.children;
  }
}

React 19: Error Boundary в функциональных компонентах

В React 19 добавлена поддержка error boundaries в функциональных компонентах через API (в разработке):

// В будущем (React 19+)
function MyErrorBoundary() {
  return useErrorHandler();
}

А пока используем class компоненты или библиотеки типа react-error-boundary.

Вывод

getDerivedStateFromError:

  • Синхронный
  • Обновляет state для UI
  • Вызывается ВО ВРЕМЯ render
  • Для управления fallback UI

componentDidCatch:

  • Асинхронный
  • Логирование и побочные эффекты
  • Вызывается ПОСЛЕ render
  • Для отправки на error tracking

Оба необходимы для полноценной обработки ошибок в React приложении!