11. 리액트에서 useEffect 객체 재생성 문제는 어떻게 해결하지
2025년 10월 2일
문제 1 :
리렌더링 시에 Filter 인스턴스가 useEffect 안에서 계속 재생성될 것 같아요.
useEffect 밖에서 useMemo를 사용해 한 번만 생성하도록 개선하면 좋을 것 같습니다.
export const NameInput = ({ value, onChange, placeholder, onValidationChange }: NameInputProps) => { const [isValid, setIsValid] = useState(false); useEffect(() => { const filter = new Filter();
현재는 useEffect안에 new Filter()로 객체를 매번 새로 만들고 있습니다.
이렇게 되면 한글자한글자 칠 때마다 filter객체를 만들게 될 것 같습니다.
useMemo 사용
const filter = useMemo(() => new Filter(), []); useEffect(() => { if (value.trim() === '') { setIsValid(false); onValidationChange?.(false); return; } const hasBadWords = filter.isProfane(value); const isValidName = !hasBadWords; setIsValid(isValidName); onValidationChange?.(isValidName); }, [value, onValidationChange, filter]);
멘토님 말씀대로, filter객체를 useMemo를 사용하여
한번 입력할 동안 여러번 filter객체를 만들지 않고 한번만 만들게 하여 성능을 높일 수 있게 변경하였습니다.
문제 2 : 조건문이 길어 가독성이 떨어지는데요. 어떻게 하면 가독성도 높이고 유지보수성도 높일 수 있을까요?
error instanceof AxiosError && error.response?.status && error.response.status >= 400 && error.response.status < 500
해당코드는 mutation의 retry를 4xx 오류에서 실행하지 않게 하기 위해 작성된 코드입니다.
이는 현재 mutation의 retry 에 전체적으로 써져있어, 가독성과 유지보수성이 떨어집니다.
가장 먼저 가독성과 유지보수성을 높이는 방법은, mutation의 값을 결정하는 로직 자체를 분리하는 것입니다.
const shouldRetry = (failureCount: number, error: unknown): boolean => { if (error instanceof AxiosError && error.response?.status && error.response.status >= 400 && error.response.status < 500) { return false; } return failureCount < 1; }; export const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, }, mutations: { retry: shouldRetry, }, }, });
shouldRetry라는 함수를 만들면, mutation의 retry 가 훨씬 깔끔해 집니다.
다만, 아직 부족하다고 생각이 듭니다.
shouldRetry안에 4xx 오류인지를 확인하는 로직 자체가 쓸데없이 길고, shouldRetry라는 이름만 보고는, 이것이 4xx오류를 거르는 로직임을 파악하기 어렵습니다.
const isClientError = (error: unknown): boolean => { return ( error instanceof AxiosError && error.response?.status != null && error.response.status >= 400 && error.response.status < 500 ); }; const shouldRetry = (failureCount: number, error: unknown): boolean => { if (isClientError(error)) { return false; } return failureCount < 1; }; export const queryClient = new QueryClient({ defaultOptions: { queries: { retry: 1, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, }, mutations: { retry: shouldRetry, }, }, });
다음과 같이 변경했습니다.
4xx 에러임을 확인하는 로직도 분리를 해, 적절한 네이밍인 isClientError를 명시함으로써, 4xx에러인지, 그리고 리트라이 여부를 결정하는 로직인지, 등을 파악하기 쉬운 네이밍과 분리가 이루어진 것 같습니다.