일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
- 객체 비교
- EnhancedFor
- 정수형타입
- exception
- 예외처리
- 자바
- 어윈 사용법
- 컬렉션프레임워크
- 한국건설관리시스템
- GRANT VIEW
- 오라클
- 예외미루기
- 사용자예외클래스생성
- 집합_SET
- 컬렉션 타입
- 제네릭
- 환경설정
- Java
- 대덕인재개발원
- 추상메서드
- 생성자오버로드
- oracle
- 참조형변수
- abstract
- 메소드오버로딩
- NestedFor
- 인터페이스
- 자동차수리시스템
- 다형성
- cursor문
- Today
- Total
거니의 velog
(35) 리덕스 툴킷 4 본문
3. 비동기 호출과 createAsyncThunk()
* useSelector() 와 useDispatch() 로 애플리케이션 상태를 이용할 때 필요한 또 다른 기능은 비동기 처리이다. 위의 예제는 API 서버를 통해서 로그인/로그아웃을 처리해야 하는 작업과 로그인 시에 API 서버를 연동한 처리가 필요하다.
* 과거 리덕스의 경우 redux-thunk나 redux-saga라는 추가적인 라이브러리를 사용해서 비동기 처리를 했지만, 리덕스 툴킷은 createAsyncThunk() 라는 기능을 사용해서 비동기 통신 상태에 따른 처리가 가능하다.
* API 서버와의 통신을 위해 프로젝트 내에 api 폴더 내에 memberApi.js 파일을 추가한다.
import axios from "axios";
import { API_SERVER_HOST } from "./todoApi";
const host = `${API_SERVER_HOST}/api/member`;
export const loginPost = async (loginParam) => {
const header = { headers: { "Content-Type": "x-www-form-urlencoded" } };
const form = new FormData();
form.append("username", loginParam.email);
form.append("password", loginParam.pw);
const res = await axios.post(`${host}/login`, form, header);
return res.data;
};
* loginSlice에서는 createAsyncThunk() 를 사용해서 비동기 통신을 호출하는 함수를 작성하고 비동기 호출의 상태에 따라 동작하는 extraReducers 를 추가해 준다.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { loginPost } from "../api/memberApi";
const initState = {
email: "",
};
export const loginPostAsync = createAsyncThunk("loginPostAsync", (param) => {
return loginPost(param);
});
const loginSlice = createSlice({
name: "LoginSlice",
initialState: initState,
reducers: {
login: (state, action) => {
console.log("login.....");
// {email, pw} 로 구성
const data = action.payload;
// 새로운 상태
return { email: data.email };
},
logout: (state, action) => {
console.log("logout....");
// 값을 초기화
return { ...initState };
},
},
extraReducers: (builder) => {
builder
.addCase(loginPostAsync.fulfilled, (state, action) => {
console.log("fulfilled");
})
.addCase(loginPostAsync.pending, (state, action) => {
console.log("pending");
})
.addCase(loginPostAsync.rejected, (state, action) => {
console.log("rejected");
});
},
});
export const { login, logout } = loginSlice.actions;
export default loginSlice.reducer;
* 변경된 loginSlice를 살펴보면 createAsyncThunk()를 사용해서 memberApi.js에 선언된 loginPost()를 호출하도록 구성하고, 아래쪽에 비동기 통신의 상태(fulfilled, 완료), pending(처리중), rejected(에러)에 따라 동작하는 함수를 작성하고 있다.
* API 서버를 실행한 상태에서 이전에 Postman에서 처리했던 /api/member/login 을 호출하도록 LoginComponent를 수정한다.
import React, { useState } from "react";
import { useDispatch } from "react-redux";
//import { login } from "../../slices/loginSlice";
import { loginPostAsync } from "../../slices/loginSlice";
(...)
const LoginComponent = () => {
const [loginParam, setLoginParam] = useState({ ...initState });
const dispatch = useDispatch();
const handleChange = (e) => {
loginParam[e.target.name] = e.target.value;
setLoginParam({ ...loginParam });
};
const handleClickLogin = (e) => {
// dispatch(login(loginParam)); // 동기화된 호출
dispatch(loginPostAsync(loginParam)); // loginSlice의 비동기 호출
};
return (
(...)
);
};
export default LoginComponent;
* 브라우저에서 로그인을 수행하면 비동기 통신이 이루어지는 것을 확인할 수 있고 API 서버에서 발생한 로그인 결과를 확인할 수 있다(실행을 위해서는 이전 장에서 작성된 API 서버를 실행해 주어야 한다).
* 콘솔창을 확인하면 서버와 통신 중인 pending과 완료를 의미하는 fulfilled가 출력된다.
(1) 로그인 후처리
* loginSlice에서는 로그인 후에 애플리케이션 상태를 처리해 주어야 한다. API 서버에서 로그인 시에 전송되는 데이터들을 상태 데이터로 보관하도록 처리한다.
extraReducers: (builder) => {
builder
.addCase(loginPostAsync.fulfilled, (state, action) => {
console.log("fulfilled");
const payload = action.payload;
return payload;
})
.addCase(loginPostAsync.pending, (state, action) => {
console.log("pending");
})
.addCase(loginPostAsync.rejected, (state, action) => {
console.log("rejected");
});
},
* 브라우저에서 로그인을 진행하기 전과 로그인 후는 메뉴 구성이 달라지는 것을 확인할 수 있다.
* 잘못된 로그인의 경우 ["error" : "..."] 메시지가 전송된다.
[unwrap()을 이용한 후처리]
* LoginComponent에서 비동기 호출 이후에 처리된 결과를 받아보려면 unwrap()을 사용할 수 있다. 예를 들어 위와 같이 error 값이 전달되는 것을 확인해야 하는 경우나 로그인 결과를 받아야 하는 경우에 유용하다.
const handleClickLogin = (e) => {
// dispatch(login(loginParam)); // 동기화된 호출
dispatch(loginPostAsync(loginParam)) // loginSlice의 비동기 호출
.unwrap()
.then((data) => {
console.log("after unwrap...");
console.log(data);
});
};
* 정상적으로 로그인이 처리되면 LoginComponent에서는 아래와 같은 로그를 출력한다.
* 로그인에 실패하는 경우에는 아래와 같은 메시지가 출력된다.
* 우선 로그인 결과에 맞게 경고창(alert)을 보이도록 수정한다.
const handleClickLogin = (e) => {
// dispatch(login(loginParam)); // 동기화된 호출
dispatch(loginPostAsync(loginParam)) // loginSlice의 비동기 호출
.unwrap()
.then((data) => {
console.log("after unwrap...");
console.log(data);
if (data.error) {
alert("이메일과 패스워드를 다시 확인하세요.");
} else {
alert("로그인 성공");
}
});
};
[로그인 후 이동 처리]
* 정상적으로 로그인이 되면 화면은 / 경로로 이동하도록 useNavigate()를 이용하는 코드를 추가한다.
import React, { useState } from "react";
import { useDispatch } from "react-redux";
//import { login } from "../../slices/loginSlice";
import { loginPostAsync } from "../../slices/loginSlice";
import { useNavigate } from "react-router-dom";
const initState = {
email: "",
pw: "",
};
const LoginComponent = () => {
const [loginParam, setLoginParam] = useState({ ...initState });
const dispatch = useDispatch();
const navigate = useNavigate();
const handleChange = (e) => {
loginParam[e.target.name] = e.target.value;
setLoginParam({ ...loginParam });
};
const handleClickLogin = (e) => {
// dispatch(login(loginParam)); // 동기화된 호출
dispatch(loginPostAsync(loginParam)) // loginSlice의 비동기 호출
.unwrap()
.then((data) => {
console.log("after unwrap...");
if (data.error) {
alert("이메일과 패스워드를 다시 확인하세요.");
} else {
alert("로그인 성공");
navigate({ pathname: `/` }, { replace: true }); // 뒤로 가기 했을 때 로그인 화면을 볼 수 없게 처리한다.
}
});
};
return (
(...)
);
};
export default LoginComponent;
* 브라우저를 이용해서 결과를 확인하면 로그인 후 자동으로 이동하게 된다.
* 로그인/로그아웃 후에는 '/' 경로로 이동하는 처리가 필요하다. 로그인 관련 이동 로직은 공통적으로 이용하는 경우가 많기 때문에 커스텀 훅스로 작성해서 사용하는 것이 편리하다.
(2) 로그인 관련 기능 처리를 위한 커스텀 훅
* 로그인이나 로그인 상태의 체크 등은 많은 컴포넌트에서 공통적으로 사용할 수 있는 기능이므로 이를 커스텀 훅으로 작성해 두면 재사용이 가능해진다.
* hooks 폴더에 useCustomLogin.js 를 추가한다.
import { useDispatch, useSelector } from "react-redux";
import { Navigate, useNavigate } from "react-router-dom";
import { loginPostAsync, logout } from "../slices/loginSlice";
const useCustomLogin = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const loginState = useSelector((state) => state.loginSlice); // 로그인 상태
const isLogin = loginState.email ? true : false; // 로그인 여부
// 로그인 함수
const doLogin = async (loginParam) => {
const action = await dispatch(loginPostAsync(loginParam));
return action.payload;
};
// 로그아웃 함수
const doLogout = () => {
dispatch(logout());
};
// 페이지 이동
const moveToPath = (path) => {
navigate({ pathname: path }, { replace: true });
};
// 로그인 페이지로 이동
const moveToLogin = () => {
navigate({ pathname: "/member/login" }, { replace: true });
};
// 로그인 페이지로 이동 컴포넌트
const moveToLoginReturn = () => {
return <Navigate replace to="/member/login" />;
};
return {
loginState,
isLogin,
doLogin,
doLogout,
moveToPath,
moveToLogin,
moveToLoginReturn,
};
};
export default useCustomLogin;
* useCustomLogin을 이용하면 LoginComponent의 코드는 조금 더 단순해 진다.
import React, { useState } from "react";
import useCustomLogin from "../../hooks/useCustomLogin";
const initState = {
email: "",
pw: "",
};
const LoginComponent = () => {
const [loginParam, setLoginParam] = useState({ ...initState });
const { doLogin, moveToPath } = useCustomLogin();
const handleChange = (e) => {
loginParam[e.target.name] = e.target.value;
setLoginParam({ ...loginParam });
};
const handleClickLogin = (e) => {
doLogin(loginParam) // loginSlice의 비동기 호출
.then((data) => {
console.log(data);
if (data.error) {
alert("이메일과 패스워드를 다시 확인하세요");
} else {
alert("로그인 성공");
moveToPath("/");
}
});
};
return (
(...)
);
};
export default LoginComponent;
(3) 로그인이 필요한 페이지
* useCustomLogin을 이용하면 로그인 체크가 필요한 페이지에서 몇 줄의 코드만으로 로그인 체크가 가능하다.
* 예를 들어 pages에 있는 AboutPage가 로그인한 사용자만이 볼 수 있는 페이지라면 아래와 같이 로그인 체크 및 이동을 처리할 수 있다.
import React from "react";
import BasicLayout from "../layouts/BasicLayout";
import useCustomLogin from "../hooks/useCustomLogin";
const AboutPage = () => {
const { isLogin, moveToLoginReturn } = useCustomLogin();
if (!isLogin) {
return moveToLoginReturn();
}
return (
<BasicLayout>
<div className="text-3xl">About Page</div>
</BasicLayout>
);
};
export default AboutPage;
* 브라우저에서 /about이라는 경로를 호출하면 /member/login 으로 이동하는 것을 볼 수 있다.
(4) 로그아웃 처리
* 로그아웃은 비동기 호출이 아니라 loginSlice의 logout()을 그대로 활용할 수 있다. 비동기 처리가 아니기 때문에 로그아웃 처리 후 / 경로로 이동하도록 구성한다.
import React from "react";
import useCustomLogin from "../../hooks/useCustomLogin";
const LogoutComponent = () => {
const { doLogout, moveToPath } = useCustomLogin();
const handleClickLogout = () => {
doLogout();
alert("로그아웃 되었습니다.");
moveToPath("/");
};
return (
(...)
);
};
export default LogoutComponent;
* 브라우저에서 로그아웃 결과를 확인한다.
'SpringBoot_React 풀스택 프로젝트' 카테고리의 다른 글
(37) 리덕스 툴킷 6 (1) | 2024.03.06 |
---|---|
(36) 리덕스 툴킷 5 (0) | 2024.03.06 |
(34) 리덕스 툴킷 3 (0) | 2024.03.06 |
(33) 리덕스 툴킷 2 (0) | 2024.03.06 |
(32) 리덕스 툴킷 1 (0) | 2024.03.06 |