Notice
Recent Posts
Recent Comments
Link
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
Archives
Today
Total
관리 메뉴

효뚜르팝의 Dev log

[항해플러스] 3주차 회고 - React, Beyond the Basics 본문

회고록

[항해플러스] 3주차 회고 - React, Beyond the Basics

hyodduru 2025. 4. 13. 21:39

 React, Beyond the Basics

feat) React 성능 최적화 & Context 리팩토링


Keep (계속 유지하고 싶은 나의 방향)

  • React.memo, useMemo, useCallback 등 리렌더링 최적화를 위한 도구들을 실제로 의도적으로 적용해본 경험.
  • 테스트 기반으로 성능 이슈를 직접 확인하고 구조를 리팩토링한 흐름. 테스트 없었으면 놓쳤을 문제들도 있었을 것 같다.
  • 유지보수와 확장성을 고려하여 기능 단위로 폴더를 나누고, 각 도메인마다 Context/Provider/hooks/UI를 분리한 구조로 설계했다.
  • 특히 useItems, useComplexForm 등 커스텀 훅으로 UI와 로직을 분리한 점이 코드 가독성이나 응집도 면에서 효과적이었던 것 같다. 
  • 이번 과제를 하며 "내가 왜 이 훅을 써야 하지?"에 대한 기술적 의문을 처음으로 스스로 던지고 고민했다는 점이 가장 의미 있었다.

Problem (고민되거나 부족했던 부분)

  • useCallback과 useMemo를 자주 써왔지만, 그 내부 구조가 동일하다는 걸 이번에 처음 제대로 인지했다.
    자주 쓰던 도구도 이해 없이 써왔다는 사실이 조금 부끄럽기도 했다.
  • 초반엔 useCallback 쓰면 무조건 최적화된다고 생각했지만,
    실제로는 의존성 배열이 유지될 때만 동일한 참조가 유지되고 최적화 효과가 발생한다는 걸 이번에야 알게 됐다.
  • NotificationContext 내부의 상태가 변하면 AuthProvider까지 함께 리렌더링되는 구조였고,
    그 결과 의도치 않게 ItemList, ComplexForm까지 리렌더링되는 테스트 실패가 발생했다.
  • 다음은 실패했던 테스트 예시다:
// 테스트 목표: 로그인/로그아웃 시 Header, ComplexForm, NotificationSystem만 리렌더링되어야 함 
expect(renderLogMock).toHaveBeenCalledWith('Header'); 
expect(renderLogMock).toHaveBeenCalledWith('ComplexForm'); 
expect(renderLogMock).toHaveBeenCalledWith('NotificationSystem');
  • 그러나 실제로는 ItemList까지 리렌더링되거나, 반대로 ComplexForm이 빠져서 테스트가 실패했다.
    • AuthProvider 내부에서 useNotificationContext()를 직접 호출하고 있었음
    • 그 의존성을 제거하기 위해 NotificationProvider 구조를 바깥으로 감쌌고
    • AuthProvider는 오직 로그인 관련 기능만 담당하도록 관심사를 분리하는 식으로 해결했다. 

🚀 Try (앞으로 더 깊게 들어가고 싶은 것들)

  • 커스텀 훅 안에서 useCallback으로 감쌀지, 컴포넌트 바깥에서 제어할지에 대한 명확한 기준을 세우고 싶다.
    지금은 약간 감으로 쓰는 느낌이라 실무에서는 구조적으로 불안할 수 있다고 생각했다.
  • 중첩된 Provider 구조에서 각 Provider의 책임을 더 분리해서 명확하게 설계하는 방법을 연습해보고 싶다.
  • Context 내부의 value 또한 useMemo, useCallback으로 철저하게 고정하여 참조 변경을 최소화했다:
 
const addNotification = useCallback((msg, type) =>
{ const id = Date.now(); setNotifications((prev) => [...prev, { id, message: msg, type }]); }, []);
const value = useMemo(() => ({ notifications, addNotification, removeNotification }),
[notifications, addNotification, removeNotification]);
  • 앞으로는 렌더링 성능을 정량적으로 추적할 수 있는 도구들 (React Profiler, why-did-you-render)도 직접 써보며 확인해보고 싶다.
  • 그리고 기술적인 구조뿐 아니라 UX 흐름(예: 로그인 후 알림이 뜨는 타이밍 등)까지 고려하는 시야를 넓히고 싶다.
    이번엔 기능 구현에 집중하다 보니 사용자 경험을 세심하게 설계하진 못했던 것 같다.

💡 새롭게 얻은 인사이트

이번 구조 리팩토링을 하며 가장 크게 얻은 인사이트 중 하나는,
Context를 반드시 전역(Global)으로 관리할 필요는 없다는 것이었다.

기능적으로 명확히 구분되는 도메인이라면,
굳이 Context로 끌어올리지 않고 커스텀 훅이나 컴포넌트 내부 상태로 관리하는 방식이 더 자연스럽고 응집력 있는 구조가 될 수 있다는 걸 직접 경험했다.

이전에는 'Context = 전역 상태'라는 생각에 모든 상태를 Context로 끌어올렸는데,
이번 과제를 통해 오히려 상황에 따라 로컬 상태를 유지하는 것이 더 낫다는 판단 기준을 세울 수 있었다.
덕분에 Context의 쓰임과 위치, 그리고 경계를 다시 생각하게 되었고,
"Context는 작게 쪼개고, 관심사는 명확히 분리한다"는 구조적 관점을 처음으로 스스로 정립하게 된 것 같다.

 

🧘🏻‍♀️ 나의 회고 한 줄

자주 쓰던 훅들의 동작 원리를 ‘이제서야’ 이해하게 됐고,
무심코 써왔던 기술들이 실제로 어떻게 작동하고, 어디에 영향을 미치는지를 고민하기 시작했다는 것,
그 자체가 내게 가장 큰 성장이었다.