React의 ErrorBoundary는 비동기 에러를 캐치를 어떻게 할까?
ErrorBoundary는 React 컴포넌트 렌더링 과정에서 발생하는 에러를 잡아내고 fallback UI를 제공하지만,
모든 종류의 에러를 캐치하지는 못합니다. 이번 포스트에서는 ErrorBoundary의 작동 범위와 한계를 이해하고, 비동기 및 이벤트 에러를 올바르게 처리하는 방법을 알아보겠습니다.
1. ErrorBoundary란?
ErrorBoundary는 React 컴포넌트 트리에서 발생하는 에러를 캐치하고,
에러가 발생했을 때 fallback UI를 표시하는 React 컴포넌트입니다.
ErrorBoundary를 사용하면 애플리케이션 전체가 중단되는 대신 에러가 발생한 부분만 격리하여 나머지 UI는 정상적으로 작동하도록 할 수 있습니다.
2. ErrorBoundary가 Catch할 수 있는 에러와 Catch하지 못하는 에러
ErrorBoundary는 모든 종류의 에러를 캐치할 수 있는 것은 아닙니다. 이는 React의 렌더링 사이클과 JavaScript 비동기 실행 시점 차이와 연관이 있습니다.
-
캐치할 수 있는 에러
React렌더링 프로세스 내에서 발생하는 에러- 컴포넌트 렌더링 중 발생하는
JavaScript런타임 에러 - 자식 컴포넌트 트리에서 발생하는 에러
-
캐치할 수 없는 에러
- 이벤트 핸들러 내부에서 발생하는 에러
- 비동기 코드(fetch, setTimeout, Promise 등)에서 발생하는 에러
- 서버 사이드 렌더링 중 발생하는 에러
3. 왜 ErrorBoundary는 비동기 에러를 캐치하지 못할까?
이는 React의 렌더링 사이클과 JavaScript 비동기 작업의 실행 시점 차이 때문입니다. JavaScript 코드는 모두 JavaScript 엔진에 의해 실행되지만, 실행 시점이 다릅니다.
3.1 React의 렌더링 사이클과 비동기 작업의 실행 시점 차이
React의 렌더링 사이클과 JavaScript 비동기 작업의 실행 시점 차이를 이해하기 위해서는 JavaScript의 실행 모델과 브라우저 환경에서의 코드 실행 구조를 더 깊이 살펴볼 필요가 있습니다.
3.2 JavaScript의 실행 모델
JavaScript는 단일 스레드 언어로, 한 번에 하나의 작업만 처리할 수 있습니다. 이는 JavaScript 엔진(예: Chrome의 V8)이 코드를 실행할 때 한 줄씩 순차적으로 처리한다는 의미입니다. 그러나 웹 애플리케이션에서는 네트워크 요청, 타이머, 사용자 이벤트 등 다양한 비동기 작업이 필요합니다.
3.3 이벤트 루프의 역할
이벤트 루프는 JavaScript의 비동기 프로그래밍 모델의 핵심 메커니즘입니다.
- 태스크 큐(Task Queue): 비동기 작업이 완료되면 해당 콜백 함수가 태스크 큐에 추가됩니다.
- 마이크로태스크 큐(Microtask Queue): Promise의 콜백과 같은 마이크로태스크가 저장됩니다.
- 이벤트 루프: 콜 스택이 비어있을 때, 이벤트 루프는 먼저 마이크로태스크 큐의 모든 작업을 처리한 다음, 태스크 큐에서 하나의 작업을 가져와 실행합니다.
3.4 React 렌더링 프로세스와 ErrorBoundary
React의 렌더링 프로세스는 다음과 같은 단계로 이루어집니다.
- 렌더 단계(Render Phase): 컴포넌트 트리를 순회하며 변경 사항을 계산합니다.
- 커밋 단계(Commit Phase): 계산된 변경 사항을 실제 DOM에 적용합니다.
이 전체 과정은 동기적으로 실행됩니다. 즉, React가 렌더링을 시작하면 모든 컴포넌트의 렌더링이 완료될 때까지 JavaScript 엔진은 다른 작업을 처리하지 않습니다.
ErrorBoundary는 이 렌더링 프로세스 중에 발생하는 에러를 캐치하도록 설계되었습니다. 구체적으로, React는 렌더 단계에서 컴포넌트를 렌더링하는 동안 발생하는 JavaScript 에러를 감지하고, 이를 가장 가까운 ErrorBoundary에 전달합니다.
3.5 비동기 작업과 ErrorBoundary의 한계
비동기 작업(fetch, setTimeout, Promise 등)은 다음과 같은 방식으로 처리됩니다.
JavaScript엔진이 비동기API(fetch, setTimeout 등)를 만나면, 해당 작업을 브라우저의 다른API(Web API)에 위임합니다.React의 렌더링 프로세스는 계속 진행되어 완료됩니다.- 비동기 작업이 완료되면, 해당 콜백 함수가 태스크 큐나 마이크로태스크 큐에 추가됩니다.
- 이벤트 루프는 콜 스택이 비어있을 때(즉,
React의 렌더링 프로세스가 완료된 후) 이 콜백 함수를 실행합니다.
이 시점에서 비동기 콜백 내에서 에러가 발생하면, 이미 React의 렌더링 사이클은 완료된 상태입니다. ErrorBoundary는 렌더링 사이클 내에서만 에러를 감지할 수 있기 때문에, 이러한 비동기 콜백에서 발생한 에러는 캐치하지 못합니다.
4. 비동기 에러를 ErrorBoundary에서 처리하는 방법
function AsyncComponent() {
const [error, setError] = useState(null);
const [data, setData] = useState(null);
useEffect(() => {
fetchData()
.then(result => setData(result))
.catch(error => setError(error));
}, []);
// 에러가 있으면 다음 렌더링 사이클에서 throw
if (error) {
throw error; // 이 에러는 Error Boundary에서 캐치됨
}
return <div>{/* 데이터 렌더링 */}</div>;
}이 패턴에서는 비동기 작업에서 발생한 에러를 상태로 저장한 다음, 다음 렌더링 사이클에서 해당 에러를 throw합니다. 이렇게 하면 에러가 React 렌더링 프로세스 내에서 발생하므로 ErrorBoundary가 캐치할 수 있습니다.
5. 실제 사용 예시
// ErrorBoundary 컴포넌트
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error caught by ErrorBoundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong!</h1>;
}
return this.props.children;
}
}// 비동기 작업을 수행하는 컴포넌트
function TodoList() {
const [todos, setTodos] = useState([]);
const [error, setError] = useState(null);
const fetchTodos = async () => {
try {
const response = await fetch('https://api.example.com/todos');
const data = await response.json();
setTodos(data);
} catch (err) {
setError(err); // 비동기 에러를 상태로 저장
}
};
// 에러가 있으면 렌더링 사이클 내에서 throw
if (error) {
throw error; // 이제 Error Boundary가 이 에러를 캐치할 수 있음
}
return (
<div>
<button onClick={fetchTodos}>Fetch Todos</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
</div>
);
}
// 애플리케이션에서 사용
function App() {
return (
<ErrorBoundary>
<TodoList />
</ErrorBoundary>
);
}위 코드에서 TodoList 컴포넌트는 비동기 작업을 수행하며, fetchTodos 함수에서 발생한 에러는 ErrorBoundary에서 캐치되지 않습니다.
따라서 비동기 작업에서 발생하는 에러는 이미 React의 렌더링 사이클이 완료된 후에 발생하므로, ErrorBoundary의 검사 범위를 벗어나게 됩니다.
그래서 ErrorBoundary에서 캐치하기 위해서는 비동기 작업에서 발생한 에러를 상태로 저장하고, 다음 렌더링 사이클에서 throw하고 렌더링 사이클에 직접 관여를 해야 ErrorBoundary에서 캐치할 수 있습니다.
6. 결론
ErrorBoundary는 React 애플리케이션의 안정성을 높이는 강력한 도구이지만, 작동원리를 이해하는 것이 중요한것 같습니다.
ErrorBoundary는 React 렌더링 사이클 내에서 발생하는 에러만 캐치할 수 있으며, 비동기 작업이나 이벤트 핸들러에서 발생하는 에러는 상태 관리를 통해 다음 렌더링 사이클로 가져와야 합니다.
댓글 로딩 중...