솔로프로젝트를 구현해보는중에 user 관련된 로직이 많이 중복되게 쓰이는걸 보다가 문득 커스텀훅으로 코드의 중복성을 줄여보고자 사용해봄!!
user 관련된 로직이 사용되는 곳
1. 로그인
2. 로그아웃
3. 헤더에서 로그인 했을때와 안했을때 상태 감지할때 user 필요
4. 마이페이지
5. 권한이 있는지 없지 확인하는 로직
지금 다 구현하지 않았음에도 여러곳에서 user 가 사용되고 있음을 확인할수 있음
user 로직을 useUser 라는 커스텀 훅을 통해 간단하게 코드를 구현해보려고 함!
export const useUser = () => {
const queryClient = useQueryClient();
// user 상태를 useQuery로 관리
const { data: user, isLoading } = useQuery({
queryKey: ["user"],
queryFn: userService.getCurrentUser,
staleTime: Infinity,
});
//로그인 요청 로직
const loginMutation = useMutation({
mutationFn: userService.loginUser,
//요청이 성공하면 user 데이터를 캐시에 저장!
onSuccess: (data) => {
console.log("useUser = ", data);
queryClient.setQueryData(["user"], data.user);
//invalidateQueries => 캐시 무효롸 해서 최신 데이터 불러옴
queryClient.invalidateQueries(["user"]);
},
});
/**
* useCallBack
* 함수를 메모이제이션(최적화) 해서 불필요하게 생성되는것을 방지
* 메모이제이션된 함수를 리턴함
*/
const login = useCallback(
(loginData) => {
console.log(loginData);
loginMutation.mutateAsync(loginData);
},
[loginMutation]
);
const logout = useCallback(() => {
localStorage.removeItem("accessToken");
localStorage.removeItem("userId");
localStorage.removeItem("userEmail");
delete axios.defaults.headers.common["Authorization"];
// 로그아웃 후 user 데이터 삭제해주기
queryClient.invalidateQueries(["user"]);
// user 상태를 null로 설정 => 초기화
queryClient.setQueryData(["user"], null);
// 추가적으로 질문 캐시 무효화
queryClient.invalidateQueries(["question"]);
}, [queryClient]);
return { user, login, logout, isLoading };
};
로그인 후 사용자 정보를 React Query 캐시에 반영하는 로직
일단 결론적으로는 위에 코드로 커스텀 훅을 생성했음
커스텀 훅에 대한 자세한 설명과 더불어 훅에 대한 간단한 설명도 적어보려고 함
일단 이번에 솔로프로젝트를 하면서 리액트 쿼리를 사용해봤음
그때 useQueryClient() 훅을 정말 많이 사용했음
useQueryClient 는 리액트 쿼리에서 전역적으로 캐시 데이터를 관리하는 queryClient 를 가져오는 훅임
useQueryClient 를 통해 캐시된 데이터를 조회, 갱신, 삭제할수 있음
1. 사용자 상태관리
const { data: user, isLoading } = useQuery({
queryKey: ["user"],
queryFn: userService.getCurrentUser,
staleTime: Infinity,
});
이때 사용된 useQuery 는
2. 로그인 요청
const loginMutation = useMutation({
mutationFn: userService.loginUser,
onSuccess: (data) => {
console.log("useUser = ", data);
queryClient.setQueryData(["user"], data.user);
queryClient.invalidateQueries(["user"]);
},
});
3. 로그인 함수 / 로그아웃 함수 => useCallback 사용
const login = useCallback(
(loginData) => {
console.log(loginData);
loginMutation.mutateAsync(loginData);
},
[loginMutation]
);
const logout = useCallback(() => {
localStorage.removeItem("accessToken");
localStorage.removeItem("userId");
localStorage.removeItem("userEmail");
delete axios.defaults.headers.common["Authorization"];
queryClient.invalidateQueries(["user"]);
queryClient.setQueryData(["user"], null);
queryClient.invalidateQueries(["question"]);
}, [queryClient]);
iuseCallback 사용하여 불필요한 함수 재생성을 방지함
loginMutation.mutateAsync(loginData) => loginMutation 을 실행하여 비동기적으로 로그인 요청을 보냄
의존성 배열에 loginMutation 넣었기에 loginMutation 이 변경되지 않는 함 login 함수는 재생성안됨
로그아웃 할때 로컬스토리지에 있는 사용자 정보 초기화 하며 헤더에서 인증에 필요했던 토큰 제거
로그인 후 사용자 정보 갱신을 위하여 invalidateQueries 사용!!!
이렇게 커스텀 훅을 사용했을때 원래 코드가 어떻게 달라졌는지 확인해보자
Header.js
const Header = () => {
const { category } = useContext(CategoryContext);
const queryClient = useQueryClient();
const { data: user } = useQuery({
queryKey: ["user"],
queryFn: userService.getCurrentUser,
staleTime: 0,
refetchOnWindowFocus: true,
});
const onLogout = () => {
localStorage.removeItem("accessToken");
localStorage.removeItem("userId");
localStorage.removeItem("userEmail");
delete axios.defaults.headers.common["Authorization"];
queryClient.invalidateQueries(["user"]);
queryClient.invalidateQueries(["question"]);
};
return(
<HeaderContainer>
<div className="arrowBox">
<ul>
{user ? (
<>
// 유저가 있을때 로직
<li onClick={onLogout} style={{ cursor: "pointer" }}>
로그아웃
</li>
</>
) : (
<>
// 유저가 없을때 로직
</>
)}
</ul>
</div>
</HeaderContainer>
)
[기존 코드]
이를 커스텀 훅을 사용하여 아래처럼 수정함
const Header = () => {
const { category } = useContext(CategoryContext);
const { user, logout } = useUser();
return(
<HeaderContainer>
<div className="arrowBox">
<ul>
{user ? (
<>
// 유저가 있을때 로직
<li onClick={logout} style={{ cursor: "pointer" }}>
로그아웃
</li>
</>
) : (
<>
// 유저가 없을때 로직
</>
)}
</ul>
</div>
</HeaderContainer>
)
Login.js 로직
export const Login = () => {
const navigate = useNavigate();
const queryClient = useQueryClient();
const loginMutation = useMutation({
mutationFn: userService.loginUser,
onSuccess: (data) => {
console.log(data.user);
queryClient.setQueryData(["user"], data.user);
queryClient.invalidateQueries(["user"]);
alert("로그인 성공!");
navigate("/");
},
onError: () => {
alert("로그인 실패!!!!");
},
});
const { login, isLoading } = useUser();
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
loginMutation.mutate(data);
try {
login(data);
alert("로그인 성공!");
navigate("/");
} catch (error) {
alert("로그인 실패!!!!");
}
};
return (
<form onSubmit={handleSubmit} style={{ marginTop: "150px" }}>
<input name="email" type="email" placeholder="이메일" required />
<input name="password" type="password" placeholder="비밀번호" required />
<button type="submit">로그인</button>
</form>
);
};
[기존 코드]
아래 또한 useUser 커스텀 훅 사용
export const Login = () => {
const navigate = useNavigate();
const { login, isLoading } = useUser();
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const data = Object.fromEntries(formData);
try {
login(data);
alert("로그인 성공!");
navigate("/");
} catch (error) {
alert("로그인 실패!!!!");
}
};
return (
<form onSubmit={handleSubmit} style={{ marginTop: "150px" }}>
<input name="email" type="email" placeholder="이메일" required />
<input name="password" type="password" placeholder="비밀번호" required />
<button type="submit" disabled={isLoading}>
{isLoading ? "로그인 중..." : "로그인"}
</button>
</form>
);
};
이런식으로 전후가 차이가 크며 지금은 두군데에서만 바꿨지만 더 큰 프로젝트를 중복된 로직 없이 관리하는 법을 아는것 또한 프론트 개발자로써 꼭 가져야 하는 마음가짐이라고 생각함!!!
'IT > React' 카테고리의 다른 글
번들링, 웹팩 (0) | 2025.02.28 |
---|---|
React - AJAX 실습(1) (0) | 2025.02.04 |
React - AJAX (0) | 2025.02.02 |
React - Hook (State, Effect) (1) | 2025.02.02 |
React - Side Effect (0) | 2025.02.01 |