useMemo(() => getStoredUser() ?? {}, [])
장단점 분석
localStorage에서 읽은 현재 사용자를 빈 의존성 배열로 메모이즈하는 패턴. "마운트 시 한 번만 계산"하려는 의도가 보이지만, useMemo는 그 의도에 맞는 도구가 아니다.
1이 코드가 의도하는 것
const currentUser = useMemo(() => getStoredUser() ?? {}, [])
| 요소 | 의도 |
|---|---|
빈 deps [] | 마운트 시 1회만 실행 → 매 렌더 localStorage 재읽기 방지 |
getStoredUser() | localStorage 읽기 + JSON.parse (동기 I/O) |
?? {} | 저장된 유저가 없으면 빈 객체로 fallback |
| useMemo 반환 | 같은 객체 참조 유지로 하위 memo/effect deps 안정화 |
2장점
- 참조 동일성(referential identity) 유지같은 객체 참조가 유지되어, 이 값을
useEffect/React.memo자식의 deps로 넘길 때 불필요한 재실행·리렌더를 막는다. 이 패턴의 거의 유일한 실질 이득. - 매 렌더 localStorage 접근 회피localStorage 읽기 + JSON.parse는 매 렌더 돌리면 누적 비용이 된다. deps가 비어 있으면 (대부분의 경우) 재실행되지 않는다.
- 간결함한 줄로 "읽고 + 캐싱"을 표현. 별도 state/effect 보일러플레이트가 없다.
3단점 핵심 문제 다수
- useMemo는 "1회 실행"을 보장하지 않는다React 공식 문서가 명시: useMemo는 성능 최적화일 뿐 캐시를 언제든 버릴 수 있다. 버려지면
getStoredUser()가 다시 호출되고{}도 새 참조로 생성된다 → "참조 안정성"이라는 장점 자체가 깨질 수 있다. "마운트 1회"를 의미론적으로 보장하지 않는다. - 값이 영구히 stale (가장 큰 실무 버그)deps가
[]라 로그인/로그아웃, 프로필 수정, 다른 탭에서의 변경이 일어나도currentUser는 마운트 시점 값에 동결된다. localStorage 변경은 원래 리렌더를 트리거하지 않으므로, 이 컴포넌트는 옛 유저를 계속 보여준다. ?? {}가 null 케이스를 은폐유저가 없을 때{}를 주면currentUser == null/!currentUser검사가 항상 false가 된다. "로그인 안 됨"을 명시적으로 다루지 못하고,currentUser.id가 조용히undefined로 흘러 버그를 뒤로 미룬다. 타입상으로도 빈 객체가 User로 위장된다.- 의미론적으로 잘못된 도구 선택"마운트 시 한 번 초기화"의 정답은
useState의 lazy initializer 또는useRef다. useMemo는 "deps 기반 재계산 캐시"용이라 의도와 도구가 어긋난다. - 이득이 미미할 수 있음이 값을 다른 deps로 쓰지 않는다면, localStorage 1회 읽기를 아끼려고 useMemo의 클로저+deps 비교 오버헤드를 추가하는 셈이라 순이득이 거의 없다.
4더 나은 대안
A. 정말 "마운트 1회 + 변경 무관"이면 → lazy useState 권장
// 초기화 함수는 첫 렌더에서만 실행 — React가 보장한다
const [currentUser] = useState(() => getStoredUser())
"1회 실행"을 진짜로 보장하고 참조도 안정적. ?? {} 대신 null을 유지해 "유저 없음"을 명시적으로 처리.
B. 변경에 반응해야 하면 → 외부 store 구독
// useSyncExternalStore: 탭 간 동기화 + 변경 시 자동 리렌더
const currentUser = useSyncExternalStore(subscribe, getStoredUser)
이 프로젝트에서는 이미
useCurrentUser() 훅이 표준이다 (CLAUDE.md: useMe()는 금지). 직접 useMemo(getStoredUser)를 쓰지 말고 useCurrentUser()를 사용하면 stale·동기화 문제를 훅 내부가 해결한다.C. React 19 Compiler 관점
React Compiler가 켜진 환경이라면 수동 메모이즈 대부분이 불필요하다. 참조 안정화를 위해 useMemo를 끼우기보다, 컴파일러에 맡기고 프로파일러로 확인된 병목에만 수동 memo를 남기는 것이 2026 베스트 프랙티스.
✓결론
이 패턴은 권장하지 않는다. 한 줄 안에 두 가지 문제가 겹쳐 있다.
- ① useMemo로 "1회 실행/참조 안정"을 보장하려는 것 → React가 보장해주지 않는 동작에 의존
- ②
?? {}+ 빈 deps → null 은폐 + 영구 stale
대체: 변경 무관이면 useState(() => getStoredUser()), 변경 반응이 필요하면 store 구독. 이 레포에선 그냥 useCurrentUser() 를 쓰면 된다.