[리팩토링] React Context 패턴으로 인증 리팩토링

Melcoding·5일 전

계기

최근 학습을 하면서 로그인을 provider로 한 번에 관리하는 구조를 알게 되어서 이를 프로젝트에 적용하기로 했다.
필요할 때마다 훅으로 불러왔는데 아래와 같이 리스너가 여러 개 붙는 구조였다.
이로 인한 문제는 각각의 리스너가 로그인 상태가 바뀌면 리스너 전부가 따로따로 반응하고, 각자 setUser를 호출하고, DB 동기화 함수도 각자 실행되어서 인증 상태 불일치 리스크가 있었고 불필요한 Firebase 호출로 요금 낭비가 발생할 수 있었다.
그래서 provider로 리스너를 딱 한 번만 등록하고, 모든 컴포넌트가 같은 상태를 공유하는 방향으로 가고자 했다.

그래서 해결 방안은?

리스너를 한 번만 등록하여 인증 상태를 전역으로 저장하고 불러오도록 하기!
그럼 전역상태관리가 필요한데 아래 2가지 방법이 있었음
1. React 자체적으로 제공하는 context 사용
2. 전역상태관리 라이브러리인 zustand 사용
왜 zustand 인가?
1) 사용이 쉽다.
2) 최근에 학습해서 써먹을 수 있다.
2가지 방법을 제시하고 AI와 논의하여 지금 현재 프로젝트의 규모와 기능을 고려했을 때, 아직 다른 전역상태 관리가 필요없기에 일단은 의존성을 가지지 않도록 라이브러리를 설치하지 않고 React에서 제공하는 context를 활용하기로 함
+context를 사용 실습도 할 겸사겸사

context 도입

AuthProvider + Context 도입 구조

AuthProvider (Firebase 리스너 1개, state 1개 소유)
│  user = { uid: "123", ... }   ← 이 하나의 값을
│
├── RootRoute        → useAuth() → Context에서 user 읽기
├── Header           → useAuth() → Context에서 user 읽기
├── HomePage         → useAuth() → Context에서 user 읽기
├── ImageUploadModal → useAuth() → Context에서 user 읽기
...

user가 바뀌면 AuthProvider 딱 한 곳에서만 업데이트되고 모든 컴포넌트는 그 값을 context를 통해서 읽는다.

AI와 같이 작업하면서 내가 판단하고 수정한 내용

AI 제안

src/
├── contexts/
│   └── AuthContext.tsx   ← AuthProvider + useAuth 실제 구현
├── hooks/
│   └── useAuth.ts        ← Context re-export (기존 import 경로 유지용)
└── main.tsx              ← <AuthProvider> 로 앱 감싸기

처음에는 위와 같이 한 파일에서 context와 provider를 하나의 파일에 집어넣는 것으로 제안하여 적용시켰는데 아래와 같이 경고가 뜸.

Fast refresh only works when a file only exports components. Move your React context(s) to a separate file.eslint(react-refresh/only-export-components)

해당 내용을 요약하면 vite에서 "컴포넌트 아닌 것도 export하고 있어서 *Fast Refresh가 제대로 못 동작할 수 있어" 경고

*Fast Refresh란

Vite 개발 서버에서 코드를 수정하면 페이지 전체를 새로고침하지 않고 바뀐 컴포넌트만 교체해주는 기능이다.
예를 들어 버튼 색상을 바꾸면, 앱 전체를 다시 시작하지 않고 그 버튼 컴포넌트만 교체해서 현재 상태(입력값, 스크롤 위치 등)를 유지한 채 변경사항을 반영한다.

// AuthContext.tsx
export const AuthContext = createContext(...)  // ← 컴포넌트 아닌 값
export default function AuthProvider(...)      // ← 컴포넌트

위와 같이 두 가지가 한 파일에 섞여 있어 경고가 발생함!

그에 대한 해결책으로 모든 것을 provider에 파일 분리 없이 넣는 것을 제안해왔는데, context, provider, hook을 한 파일에 작성하는 것!

결론적으로 AI의 제안 기각하고 모두 분리

src/context/
├── AuthContext.ts      ← createContext만
├── AuthProvider.tsx    ← Provider 컴포넌트만
└── useAuth.ts          ← 훅만

AI가 제안한 '한 파일에 모든 것을 넣자'는 의견을 기각 시키고, 오히려 위와 같이 모든 것을 나누는 형태로 작업함
그렇게 판단한 이유는 하나에 모든 것을 집약시키면 이후에 수정할 때 어디서 문제가 발생했는지 디버깅하는데 더 어렵다고 판단했음.
실제로 이전에 로그인 오류가 발생하여 롤백했을 때, 여러 군데 코드가 파편화 되어 있어서 여기서 문제가 발생할 수도 있고 저기서 문제가 발생할 수도 있음을 직접적으로 경험하였음.
그래서 하나의 파일이 아닌 최대한 한 파일에 하나의 기능만 하도록 하는 형태가 필요하다고 판단하였고 모두 분리하는 것으로 결론을 내림.

훅은 왜 그대로 둠?

  • 이미 useAuth를 많은 곳에서 사용하고 있어서 해당 코드만 바꾸면 되서 그냥 둠
  • 에러처리 안에다가 넣으면 useAuth()만 사용하면 에러 굳이 안 적어도 됨.

결과

분리하여 리팩토링한 결과 아래와 같이 리스너가 한 번만 등록이 되고 state가 전역 상태로 관리됨!

속시원...

번외

앞서 진행했던 [트러블슈팅] Firebase 멀티호스팅 리다이렉트 로그인 오류 완벽 해결에서 리다이렉트 로그인 원인 파악 덕분에 실제 서비스 배포 전 테스트가 가능하여 한결 수월하게 작업했음.

profile
https://github.com/meldyssey

0개의 댓글