프로젝트를 인수인계 받아서 작업 중인데, 작업 목록 중 이미지 수정 시 무한 렌더링 오류 발생 항목이 있었다. 이전부터 오류를 인지하고는 있었는데, 어떤 조건으로 발생하는 오류인지, 원인이 뭔지는 모른다고 하셨다. 내가 작업하기로 하여 일단 조건부터 찾아보려고 하였는데 찾을 것도 없이 이미지 수정만 하면 무조건 발생하는 오류였다.
오류 로그를 보니 useEffect 활용 관련해서 문제가 발생하는 같았다. 그런데 오류 발생 코드에 useEffect가 5개 이상 선언되어 있어서 찾기 너무 어려웠고..모든 useEffect 내에서 콘솔을 찍어 문제를 유발하는 useEffect를 찾아내었다.
콘솔을 찍어보니 문제 발생 컴포넌트에서 무한 렌더링 되어 일정 스택을 찍으면 아래 오류 메시지가 출력되는 구조였다. (208스택인가)
useEffect
`useEffect`는 React 컴포넌트가 렌더링 될 때 특정 동작을 수행하도록 하는 훅이다. `useEffect`를 사용하여 컴포넌트가 화면에 그려지거나 상태가 변경될 때 원하는 작업을 실행할 수 있다. 데이터 fetch, 이벤트 리스너 등록 및 해제, 타이머 설정 등 사이드 이펙트를 다룰 때 주로 사용된다.
`useEffect`는 비동기적으로 동작하여, 렌더링 흐름을 막지 않으며 필요에 따라 컴포넌트가 업데이트 될 때나 언마운트 될 때 특정 작업을 수행할 수 있다.
의존성 배열
의존성 배열은 useEffect가 실행되는 시점을 결정한다. 배열 안에 변수나 상태를 넣으면, 그 값이 변경될 때마다 해당 `useEffect`가 다시 실행된다. 의존성 배열을 빈 배열 `[]`로 설정하면, 컴포넌트가 처음 렌더링 될 때 한 번 실행된다. 의존성 배열을 적절히 설정하여, 불필요한 리렌더링을 방지할 수 있다.
의존성 배열에 이벤트 핸들러를 넣으면
이번 오류는 의존성 배열에 이벤트 핸들러가 들어가서 발생한 문제였다. 처음 문제를 해결할 때는, 계속해서 변화를 유발하는 값이 있어 리렌더링이 무한으로 발생하는 구나->의존성 배열 내부 값 중 계속해서 변화 가능한 요소로 보이는 onChange를 지워보자 라는 생각으로 onChange를 지웠고, 실제로 문제가 해결되었다. 문제가 해결된 건 좋은데, 왜 onChange가 문제를 일으켰던 것인지 궁금해서 공부를 해보았다.
의존성 배열에 이벤트핸들러(onChange 등)를 포함한 것 자체가 문제라기보단, onChange 동작이 문제였던 것으로 보인다. 이미지 파일을 업로드하고, 미리보기를 제공하는 기능을 구현하면서 커스텀 훅을 사용했는데, 다음 형식으로 구현되었다.
useEffect(() => {
if (onChange && event) onChange(event, preview);
}, [onChange, preview, event]);
이 코드에서 `useEffect`는 onChange, preview, event가 의존성 배열에 포함되어 있어, 이 세 값 중 하나라도 변경될 때마다 실행된다. 문제가 되는 onChange는 파일이 변경될 때마다 새로운 참조값을 가지기 때문에 `useEffect`가 계속 재실행되며, 무한 렌더링에 빠지게 된 것이다. 구체적으로는
1. 이미지 수정이 발생하여 onChange가 호출되고, 그 결과 새로운 참조값의 onChange생성
2. useEffect는 onChange가 의존성 배열에 포함되어 있으므로, 참조값이 달라졌다 판단하여 다시 실행
3. useEffect가 실행되면서 onChange가 다시 호출되고, 또 새로운 참조값 생성
4. 위 과정 반복으로 무한 루프에 빠지고, 무한 렌더링이 발생
해결
해결은 간단하게 onChange를 지워서 해결했다. preview, event만으로도 의도한 기능이 수행되고, 부수 효과를 걱정하여 관련한 모든 기능을 테스트 하였는데 문제가 없었다.
onChange의 참조값을 고정하여 파일이 변경되더라도 변하지 않도록 useCallback으로 메모이제이션 하는 방법도 있었으나 코드 간 연관관계가 복잡하여 제일 간단하고 방법을 택했다.
이 문제를 해결하면서, `useEffect`의 의존성 배열에 포함된 이벤트 핸들러가 예상치 못한 무한 렌더링을 유발할 수 있다는 점을 배웠다. 한 번도 이런 식으로 코드를 작성해보지 않았고, 생각치도 않았던 부분이라 오류를 해결하면서 많은 것을 배웠던 것 같다. 문제는 의존성 배열 내 onChange를 지워서 간단하게 해결했지만, 앞으로 의존성 배열에 이벤트 핸들러나 함수를 포함할 일이 있을 땐, 참조값이변하지 않도록 `useCallback`이나 `useMemo`를 사용하여 리렌더링 최적화, 무한 루프 방지 처리를 꼭 신경써야겠다.
'글또' 카테고리의 다른 글
Three.js를 시작해보자 (0) | 2024.10.27 |
---|---|
[글또] 2주 회고와 글또 10기 다짐 (4) | 2024.10.13 |