실습 가이드: 16강 에서 useMemo의 역할은 무엇인가요?
이 실습에서 말하는 것은 "상태 변경용(dispatch) 컴포넌트와 상태 사용용(state) 컴포넌트를 분리해서 불필요한 리렌더링을 줄이는 것"으로 받아들였는데요
그렇다면 이 코드에서 useMemo는 어떤 동작을 하며 역할은 무엇인가요?
Answer 3
0
안녕하세요 이태관님. 남겨주신 질문을 보면, 강의에서 다룬 '상태와 상태 변경 컴포넌트 분리를 통한 리렌더링 최적화'라는 목적을 정확하게 파악하셨습니다.
제가 제공해드린 실습 코드에서 useMemo가 정확히 어떤 역할을 하도록 의도했는지, 그리고 실무에서는 이 코드를 어떻게 바라보고 적용하는지 코드를 통해 단계별로 설명해 드리겠습니다.
제가 실습 가이드 상에서 useMemo를 통해 의도한 역할은 참조 유지를 통한 리렌더링 방어입니다. React의 렌더링 엔진은 Context Provider의 값으로 전달되는 속성의 참조 주소가 변경될 때마다, 해당 Context를 구독하는 모든 하위 컴포넌트를 강제로 리렌더링하는 특성이 있습니다.
// 실습 코드에서의 useMemo 사용
// 상태 객체의 참조를 유지하려는 '의도'
const memoizedState = useMemo(() => state, [state]);
return (
<AuthStateContext.Provider value={memoizedState}>
{/* ... */}
</AuthStateContext.Provider>
);
따라서 제가 실습 코드에서 useMemo를 사용한 근본적인 목적은, 상태 객체가 가지고 있는 값이 실제로 변하지 않았다면 컴포넌트가 다시 호출되더라도 이전과 동일한 메모리 주소를 반환하도록 강제하는 데 있습니다. 하위 컴포넌트들이 불필요하게 리렌더링되는 것을 방어하는 역할을 하도록 의도한 것입니다.
하지만 참조 동등성을 유지해야 한다는 개념을 교육하기 위해 명시적으로 작성해둔 이 useMemo 코드는, React의 내부 동작 원리를 고려했을 때 사실상 아무런 역할도 하지 않는 코드에 가깝습니다. 그 이유는 useReducer가 가진 고유의 특성 때문입니다.
const [state, dispatch] = useReducer(authReducer, {
user: null,
isLoading: false,
error: null,
});
// useReducer는 이미 상태가 안 변하면 참조를 유지합니다.
// 상태가 변하면 어차피 memoizedState도 새로 갱신되므로,
// 여기서의 useMemo는 사실상 동작에 영향을 주지 않습니다.
const memoizedState = useMemo(() => state, [state]);
React 내부적으로 useReducer나 useState가 반환하는 상태 객체는 새로운 상태로 업데이트되지 않는 이상, 렌더링 간에 이미 동일한 참조를 유지하도록 보장됩니다. 만약 dispatch 명령이 호출되어 상태가 변경된다면 useReducer는 새로운 상태 객체를 반환하게 되고, 이때 useMemo의 의존성 배열([state])에 등록된 상태 값이 변경되었으므로 useMemo 역시 콜백을 재실행하여 새로운 객체를 반환하게 됩니다.
즉, 상태가 변하지 않으면 애초에 참조가 같고, 상태가 변하면 useMemo도 어차피 새로운 참조를 만들어내기 때문에 해당 위치에서의 useMemo 연산은 실질적인 의미가 없습니다. 결과적으로 제가 구성한 이 아키텍처에서 리렌더링 최적화를 완성한 진짜 이유는 useMemo가 아니라 Context를 상태용과 명령용 두 개로 쪼갠 구조 그 자체입니다. useReducer가 반환하는 dispatch 함수는 컴포넌트의 생애주기 동안 절대 참조가 변하지 않기 때문에 채널을 분리한 것만으로도 최적화가 이루어집니다.
그렇다면 실무에서 Context를 최적화할 때 useMemo가 반드시 필요한 상황은 언제일까요? 보통 useReducer를 쓰지 않고 여러 개의 개별 상태를 조합해서 하나의 객체로 내려보낼 때 사용됩니다.
예를 들어 Provider 내부에 유저 정보와 로딩 상태라는 두 개의 독립적인 상태가 있다고 가정해 보겠습니다.
// 나쁜 예 (렌더링 폭탄 발생)
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// 객체 리터럴 { user, isLoading }을 직접 전달하면
// AuthProvider가 렌더링될 때마다 완전히 새로운 메모리 주소를 가진 객체가 생성됩니다.
return (
<AuthContext.Provider value={{ user, isLoading, setUser }}>
{children}
</AuthContext.Provider>
);
}
위 코드처럼 값 속성에 바로 객체 형태로 전달하게 되면 컴포넌트가 렌더링될 때마다 새로운 객체가 메모리에 계속 생성됩니다. 이렇게 되면 이 Context를 구독하는 모든 하위 컴포넌트는 실제 값이 바뀌지 않았음에도 불구하고 무조건 리렌더링이 발생하는 문제가 생깁니다.
이러한 문제를 해결하기 위한 useMemo의 진짜 역할은, 내부 값이 실제로 바뀔 때만 새로운 객체를 생성하도록 묶어주는 것입니다.
// 해결책 (useMemo의 올바른 활용)
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// user나 isLoading 값이 '실제로' 바뀔 때만 새로운 객체를 생성하고,
// 값이 같다면 이전 렌더링에서 만들어둔 객체의 참조 주소를 그대로 재사용합니다.
const value = useMemo(
() => ({ user, isLoading, setUser }),
[user, isLoading]
);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
변수에 useMemo를 사용하여 반환할 객체와 의존성 배열을 설정해두면, 내부 값이 같을 경우 이전 렌더링에서 만들어둔 객체의 참조 주소를 그대로 재사용하게 됩니다. 이를 통해 Provider에 여러 상태를 객체로 묶어 전달할 때 참조가 깨지지 않도록 방어할 수 있습니다.
요약하자면 채널을 분리하여 리렌더링을 막는다는 아키텍처의 방향성은 정확합니다. 실습 코드에 포함된 useMemo는 "객체를 넘길 때는 참조가 깨지지 않게 주의해야 한다"는 점을 설명하기 위해 제가 추가해둔 교육용 장치로 이해하시면 됩니다.
현업 실무 환경에서는 위와 같이 Context API를 쪼개고 useMemo로 감싸는 작업이 번거롭고 보일러플레이트 코드가 길어질 수 있습니다. 그래서 전역 상태 관리가 복잡해지는 프로젝트에서는 Zustand나 Jotai 같은 상태 관리 라이브러리를 도입하는 경우가 많습니다. 이 라이브러리들은 필요한 상태만 골라서 구독할 수 있도록 내부적으로 렌더링 최적화가 적용되어 있습니다.
이처럼 Context API의 한계와 렌더링 최적화 원리(참조 동등성)를 이해하고 상태 관리 도구를 활용하는 것은 실무에서 발생하는 렌더링 이슈를 트러블슈팅하는 데 핵심적인 기반이 됩니다. 학습에 참고가 되시길 바랍니다.
0
안녕하세요 이태관님. 남겨주신 질문을 보면, 강의에서 다룬 '상태와 상태 변경 컴포넌트 분리를 통한 리렌더링 최적화'라는 목적을 정확하게 파악하셨습니다.
제가 제공해드린 실습 코드에서 useMemo가 정확히 어떤 역할을 하도록 의도했는지, 그리고 실무에서는 이 코드를 어떻게 바라보고 적용하는지 코드를 통해 단계별로 설명해 드리겠습니다.
제가 실습 가이드 상에서 useMemo를 통해 의도한 역할은 참조 유지를 통한 리렌더링 방어입니다. React의 렌더링 엔진은 Context Provider의 값으로 전달되는 속성의 참조 주소가 변경될 때마다, 해당 Context를 구독하는 모든 하위 컴포넌트를 강제로 리렌더링하는 특성이 있습니다.
// 실습 코드에서의 useMemo 사용
// 상태 객체의 참조를 유지하려는 '의도'
const memoizedState = useMemo(() => state, [state]);
return (
<AuthStateContext.Provider value={memoizedState}>
{/* ... */}
</AuthStateContext.Provider>
);
따라서 제가 실습 코드에서 useMemo를 사용한 근본적인 목적은, 상태 객체가 가지고 있는 값이 실제로 변하지 않았다면 컴포넌트가 다시 호출되더라도 이전과 동일한 메모리 주소를 반환하도록 강제하는 데 있습니다. 하위 컴포넌트들이 불필요하게 리렌더링되는 것을 방어하는 역할을 하도록 의도한 것입니다.
하지만 참조 동등성을 유지해야 한다는 개념을 교육하기 위해 명시적으로 작성해둔 이 useMemo 코드는, React의 내부 동작 원리를 고려했을 때 사실상 아무런 역할도 하지 않는 코드에 가깝습니다. 그 이유는 useReducer가 가진 고유의 특성 때문입니다.
const [state, dispatch] = useReducer(authReducer, {
user: null,
isLoading: false,
error: null,
});
// useReducer는 이미 상태가 안 변하면 참조를 유지합니다.
// 상태가 변하면 어차피 memoizedState도 새로 갱신되므로,
// 여기서의 useMemo는 사실상 동작에 영향을 주지 않습니다.
const memoizedState = useMemo(() => state, [state]);
React 내부적으로 useReducer나 useState가 반환하는 상태 객체는 새로운 상태로 업데이트되지 않는 이상, 렌더링 간에 이미 동일한 참조를 유지하도록 보장됩니다. 만약 dispatch 명령이 호출되어 상태가 변경된다면 useReducer는 새로운 상태 객체를 반환하게 되고, 이때 useMemo의 의존성 배열([state])에 등록된 상태 값이 변경되었으므로 useMemo 역시 콜백을 재실행하여 새로운 객체를 반환하게 됩니다.
즉, 상태가 변하지 않으면 애초에 참조가 같고, 상태가 변하면 useMemo도 어차피 새로운 참조를 만들어내기 때문에 해당 위치에서의 useMemo 연산은 실질적인 의미가 없습니다. 결과적으로 제가 구성한 이 아키텍처에서 리렌더링 최적화를 완성한 진짜 이유는 useMemo가 아니라 Context를 상태용과 명령용 두 개로 쪼갠 구조 그 자체입니다. useReducer가 반환하는 dispatch 함수는 컴포넌트의 생애주기 동안 절대 참조가 변하지 않기 때문에 채널을 분리한 것만으로도 최적화가 이루어집니다.
그렇다면 실무에서 Context를 최적화할 때 useMemo가 반드시 필요한 상황은 언제일까요? 보통 useReducer를 쓰지 않고 여러 개의 개별 상태를 조합해서 하나의 객체로 내려보낼 때 사용됩니다.
예를 들어 Provider 내부에 유저 정보와 로딩 상태라는 두 개의 독립적인 상태가 있다고 가정해 보겠습니다.
// 나쁜 예 (렌더링 폭탄 발생)
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// 객체 리터럴 { user, isLoading }을 직접 전달하면
// AuthProvider가 렌더링될 때마다 완전히 새로운 메모리 주소를 가진 객체가 생성됩니다.
return (
<AuthContext.Provider value={{ user, isLoading, setUser }}>
{children}
</AuthContext.Provider>
);
}
위 코드처럼 값 속성에 바로 객체 형태로 전달하게 되면 컴포넌트가 렌더링될 때마다 새로운 객체가 메모리에 계속 생성됩니다. 이렇게 되면 이 Context를 구독하는 모든 하위 컴포넌트는 실제 값이 바뀌지 않았음에도 불구하고 무조건 리렌더링이 발생하는 문제가 생깁니다.
이러한 문제를 해결하기 위한 useMemo의 진짜 역할은, 내부 값이 실제로 바뀔 때만 새로운 객체를 생성하도록 묶어주는 것입니다.
// 해결책 (useMemo의 올바른 활용)
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// user나 isLoading 값이 '실제로' 바뀔 때만 새로운 객체를 생성하고,
// 값이 같다면 이전 렌더링에서 만들어둔 객체의 참조 주소를 그대로 재사용합니다.
const value = useMemo(
() => ({ user, isLoading, setUser }),
[user, isLoading]
);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
변수에 useMemo를 사용하여 반환할 객체와 의존성 배열을 설정해두면, 내부 값이 같을 경우 이전 렌더링에서 만들어둔 객체의 참조 주소를 그대로 재사용하게 됩니다. 이를 통해 Provider에 여러 상태를 객체로 묶어 전달할 때 참조가 깨지지 않도록 방어할 수 있습니다.
요약하자면 채널을 분리하여 리렌더링을 막는다는 아키텍처의 방향성은 정확합니다. 실습 코드에 포함된 useMemo는 "객체를 넘길 때는 참조가 깨지지 않게 주의해야 한다"는 점을 설명하기 위해 제가 추가해둔 교육용 장치로 이해하시면 됩니다.
현업 실무 환경에서는 위와 같이 Context API를 쪼개고 useMemo로 감싸는 작업이 번거롭고 보일러플레이트 코드가 길어질 수 있습니다. 그래서 전역 상태 관리가 복잡해지는 프로젝트에서는 Zustand나 Jotai 같은 상태 관리 라이브러리를 도입하는 경우가 많습니다. 이 라이브러리들은 필요한 상태만 골라서 구독할 수 있도록 내부적으로 렌더링 최적화가 적용되어 있습니다.
이처럼 Context API의 한계와 렌더링 최적화 원리(참조 동등성)를 이해하고 상태 관리 도구를 활용하는 것은 실무에서 발생하는 렌더링 이슈를 트러블슈팅하는 데 핵심적인 기반이 됩니다. 학습에 참고가 되시길 바랍니다.
0
안녕하세요, 인프런 AI 인턴입니다. 이태관님.
useMemo는 컴포넌트에서 연산량이 많은 계산을 최적화하기 위해 사용하는 React Hook입니다. 사용된 값이 변경되지 않는 한, 이전에 계산한 결과를 재사용하여 불필요한 재계산을 방지합니다.
특히 상태 변경용(dispatch) 컴포넌트와 상태 사용용(state) 컴포넌트를 분리할 때, useMemo를 사용하여 매번 동일한 연산을 할 필요가 없는 경우에 성능을 최적화할 수 있습니다. 이는 불필요한 리렌더링을 줄여 성능을 높이는 데 기여합니다.
예를 들어, 컴포넌트에서 복잡한 계산 결과를 useMemo로 메모이제이션 함으로써, 의존 값이 변경될 때만 재계산하게 됩니다. 이렇게 하면 데이터가 변경되지 않으면 메모이제이션된 값을 그대로 사용하여, 불필요한 렌더링을 효과적으로 줄일 수 있습니다.
관련 예시와 자세한 설명을 원하시면 다음 강의의 내용을 참고해 보세요: 강의 내용: React 최적화 기법과 useMemo 활용.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
vercel new project 에 노출되지 않으면 어떻게 해야 할까요?
0
17
1
소스코드 어디서 다운받아요?
0
13
5
동영상 끊김 ( 섹션 2 )
0
15
2
supabase 다른 프로젝트 적용 관련 질문드려요.
0
21
1
시스템관리자가 앱을 차단했다고 뜹니다.
0
14
1
프로젝트 폴더 복사 후 사용 관련
0
19
2
기술스택 강의 관련해서 질문드려요.
0
22
2
강의 자료를 찾을 수 없습니다ㅠㅠ
0
22
2
서브에이전트 문의
0
22
2
노션 강의 화면과 실제 화면과 너무 달라서 수업 진행이 안 됩니다
0
29
1
파워셀에서 claude 코드의 버전확인이 않됩니다.
0
25
2
윈도우에서 설정화면이 다릅니다.클로드코드 환경변수 설정
0
23
2
React 와 Virtual DOM 의 이야기 영상 실행이 안됩니다.
0
19
1
깃허브 Publish 질문
0
29
2
클로드 코드 프로 사용자인데..
0
35
2
강의는 순서대로 들어야 할까요??
0
35
1
supabase 사용 관련.
0
32
2
상태(State) 가 "시간이 지남~" 에 대해 질문 있습니다.
0
24
2
문서 업데이트
0
37
2
useReducer와 커스텀훅
0
46
1
미션 14에서 StockButton의 memo는 어떤 역할인가요
0
33
2
useReducer가 race condition을 해소하는 예시
0
99
1
useRef를 활용한 이전 상태 추적 시 발생하는 ESLint 에러(react-hooks/refs)에 대해 질문드립니다.
0
153
1
미션18
0
73
2

