-
Notifications
You must be signed in to change notification settings - Fork 0
[♻️ Refactor] user 코드 개선 #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| // ❌ 안티패턴: 모든 계층이 뒤섞임 | ||
| "use client"; | ||
|
|
||
| function ProfilePage() { | ||
| const [user, setUser] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| // 1. Infrastructure (localStorage) | ||
| const token = localStorage.getItem('accessToken'); | ||
|
|
||
| // 2. Domain Logic (토큰 검증) | ||
| if (!token || isTokenExpired(token)) { | ||
| router.push('/login'); | ||
| return; | ||
| } | ||
| // 3. Infrastructure (fetch) | ||
| fetch('/api/user', { | ||
| headers: { Authorization: `Bearer ${token}` } | ||
| }) | ||
| // 4. Presentation Logic | ||
| .then(res => res.json()) | ||
| .then(data => setUser(data)); | ||
| }, []); | ||
|
|
||
| return <div>{user?.name}</div>; | ||
| } | ||
|
|
||
|
|
||
| // ♻️ Refactor | ||
|
|
||
| // 기존에는 ProfilePage 안에서 토큰 확인 + fetch + 리다이렉트를 모두 처리하고있음 | ||
|
|
||
| // services/storage.ts | ||
| // 저장소 역할만 담당 | ||
| // 토큰을 어디서 읽는지만 담당 | ||
|
|
||
| // 추후에 뭔가 변화가 생긴다면 모든 페이지에서 수정하는 것이 아닌 이곳에서만 수정하면 | ||
| // 한번에 해결됨 | ||
| export const storage = { | ||
| getToken: () => localStorage.getItem('accessToken'), | ||
| }; | ||
|
|
||
| // services/api.ts | ||
| // 서버와 통신만을 책임짐 | ||
| // 백엔드와 통신만을 담당 | ||
|
|
||
| // Pages, Hooks, Components 어디서든 API 로직 재사용 가능 | ||
| // 에러 핸들링 일원화 가능 | ||
| // 추후에 axios 같은 라이브러리로 변경시 여기에서만 바꿔주면 됨 | ||
| export const api = { | ||
| getUser: async (token: string) => { | ||
| const res = await fetch('/api/user', { | ||
| headers: { Authorization: `Bearer ${token}` } | ||
| }); | ||
| if (!res.ok) throw new Error('Failed to fetch'); | ||
| return res.json(); | ||
| } | ||
| }; | ||
|
Comment on lines
+39
to
+58
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 객체 내부에 함수 선언하는 방식을 선호하시나요?!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 그런건 없고 필요에 따라 사용하는 것 같아요! |
||
|
|
||
| // hooks/useUser.ts | ||
| // 유저 정보를 react-query로 관리 | ||
| // 토큰을 기반으로 유저 정보 가져오는 로직만 담당 | ||
|
|
||
| // 이제 페이지에서 fetch 관리 안 해도됨 | ||
| import { useQuery } from '@tanstack/react-query'; | ||
|
|
||
| export function useUser() { | ||
| const token = storage.getToken(); | ||
|
|
||
| return useQuery({ | ||
| queryKey: ['user', token], | ||
| queryFn: () => api.getUser(token!), | ||
| enabled: !!token && !isTokenExpired(token), | ||
| }); | ||
|
Comment on lines
+70
to
+74
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 리액트 쿼리는 이렇게 사용하는거군요...😲 |
||
| } | ||
|
|
||
| // components/ProtectedRoute.tsx | ||
| // 공통 라우트 보호 처리 | ||
| // 토큰 체크 후 보호 라우트 처리만 담당 | ||
|
|
||
| // 모든 페이지에 같은 로그인 체크 로직을 넣을 필요가 없음 | ||
| // 재사용 가능 | ||
| export function ProtectedRoute({ children }) { | ||
| const router = useRouter(); | ||
| const token = storage.getToken(); | ||
|
|
||
| useEffect(() => { | ||
| if (!token || isTokenExpired(token)) { | ||
| router.push('/login'); | ||
| } | ||
| }, [token]); | ||
|
|
||
| if (!token) return null; | ||
| return children; | ||
| } | ||
|
|
||
|
Comment on lines
+83
to
+96
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 이렇게 Route로 하나 더 분리해볼 수도 있겠네요!!!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 저도 gpt의 도움을 받아서 알게된 사실이에요! |
||
| // ProfilePage.tsx | ||
| // 페이지 본연의 역할만 담당 | ||
| // 렌더만 담당 | ||
|
|
||
| // 읽기 쉬워짐, 유지보수 쉬워짐, 테스트가 쉬워짐 | ||
| // 난잡하게 로직이 섞여져있던 형태가 사라짐 | ||
| "use client"; | ||
| export function ProfilePage() { | ||
| const { data: user } = useUser(); | ||
|
|
||
| return ( | ||
| <ProtectedRoute> | ||
| <div>{user?.name}</div> | ||
| </ProtectedRoute> | ||
| ); | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
만약 앱라우터 환경에서 사용하는거라고 가정한다면
window 타입 체크해도 좋을 것 같아요!
아니면 getToken을 사용하는 곳에서는 'use Client'를 적어야 한다고 명시해도 좋구요!