과거에는 리액트에서 컴포넌트 내부에서 에러가 발생하면 이러한 에러를 처리할 수 있는 방식을 제공해주지 않고 어플리케이션 전체를 중단시키곤 했다.
그래서 한 곳에서 발생한 오류로 인해 전체가 중단되는 문제점을 해결하기 위해 등장한 것이 Error Boundary 이다.
Error Boundary에는 하위 컴포넌트 트리의 어디에서든 발생한 에러를 catch하는 생명주기 메서드를 제공해준다.
자식 컴포넌트에서 에러가 발생되었을 때 호출되며 여기서 Error를 캡처하여 fabllack UI를 보여줄 수 있다.
static getDerivedStateFromError(): State {
return { hasError: true };
}
render() {
const { hasError } = this.state;
const { children } = this.props;
if (hasError) return fallback UI
return children;
}
위의 getDerivedStateFromError() 생명주기 메서드와 같이 자식 컴포넌트에서 에러가 발생되었을 때 호출되는건 동일하지만 에러 정보를 기록하는 등의 side effect 용도로 사용한다.
getDervivedStateFromError와 componentDidCatch 차이점이 궁금하다면 Brian Vaughn 씨가 직접 답변 주신 내용을 확인하면 좋을 것 같다.
나의 경우에는 해당 메서드내에서 error 종류에 따라 toast Message를 호출하는 side effect를 사용했었다.
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
if (error instanceof CustomError) error.activateHandler();
}
Error Boundary의 등장이유를 생각해보면 한곳에서 발생한 에러로 인해 전체 어플리케이션을 중단시키는일을 방지하고자 생겨났기 때문에 개발자의 의도에 따라 원하는 곳에 배치할 수 있다.
나의 경우에는 Youtube API를 호출하는 컴포넌트 상위에 Error Boundary를 배치하여 에러를 처리하고 있다.
<Styled.SearchResultWrapper>
<Styled.MainTitle>검색 결과</Styled.MainTitle>
<ErrorBoundary fallback={<Error />}>
<SearchResultContainer
keyword={keyword}
handleSelectMusic={handleSelectMusic}
/>
</ErrorBoundary>
</Styled.SearchResultWrapper>
import React, { Component, ErrorInfo, ReactElement, ReactNode } from 'react';
import CustomError from '../../../utils/customError';
interface Props {
children: ReactNode;
fallback: ReactElement;
isReload?: boolean;
}
interface State {
hasError: boolean;
}
class ErrorBoundary extends Component<Props, State> {
state: State = {
hasError: false,
};
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
if (error instanceof CustomError) error.activateHandler();
}
resetBoundary = () => {
this.setState({ hasError: false });
};
reloadBoundary = () => window.location.reload();
render() {
const { hasError } = this.state;
const { children, fallback, isReload } = this.props;
if (hasError)
return React.cloneElement(fallback, {
refresh: isReload ? this.reloadBoundary : this.resetBoundary,
});
return children;
}
}
export default ErrorBoundary;