<리듀서 상태를 업데이트 하는 함수>
아래와 같이 하면 case 별로 업데이트를 하게된다. 단, 예시는 ADD CART만 있기때문에 CLEAR 하면 모든것을 지운는 것이다.
// 리듀서 함수 정의
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_CART':
const existProduct = state.productsInCart.find(
(p) => p.id === action.product.id,
);
let updatedProduct;
let totalQuantity = (state.totalQuantity += action.product.quantity);
if (existProduct) {
// 상품이 이미 카트에 존재한다면
// 다른 상품은 그대로 유지, id가 같은 상품의 quantity만 수정
updatedProduct = state.productsInCart.map((p) =>
p.id === action.product.id
? { ...p, quantity: p.quantity + action.product.quantity }
: p,
);
} else {
// 상품이 처음 카트에 담기는 거라면
updatedProduct = [...state.productsInCart, action.product];
}
// 세션 스토리지에 상태 저장
// 로컬, 세션 스토리지에는 문자열만 저장할 수 있습니다. (객체, 배열은 저장 안됨)
// JSON 문자열로 변환해서 객체, 배열을 저장
sessionStorage.setItem('productsInCart', JSON.stringify(updatedProduct));
sessionStorage.setItem('totalQuantity', totalQuantity);
return {
productsInCart: updatedProduct,
totalQuantity,
};
case 'CLEAR_CART':
sessionStorage.removeItem('productsInCart');
sessionStorage.removeItem('totalQuantity');
return {
productsInCart: [],
totalQuantity: 0,
};
}
};
리듀서 함수란 결국 업데이트를 지속적으로 시켜주는 함수라고 생각하면 될듯싶다.
장바구니 기능이기 때문에 2개만 존재하는데 CLEAR_CART 기능과 ADD_CART 기능이다.
CLEAR_CART 기능은 결국 session에 저장한 내용을 지우는 업무를 처리한다고 생각하면 좋을 것같다.
ADD_CART 기능은 session에 JSON문자로 변환해서 저장하는 것을 볼수있는데 주석에 달린것처럼 세션과 로컬 스토리지는 문자열만 저장할수있기 때문이다.
<axios>
try {
await axios.post(
`${process.env.REACT_APP_API_BASE_URL}/order/create`,
orderProducts,
{
headers: {
Authorization: 'Bearer ' + localStorage.getItem('ACCESS_TOKEN'),
},
},
);
alert('주문이 완료되었습니다.');
clearCart();
} catch (e) {
console.log(e);
alert('주문 실패!');
}
};
위와 같이 작성을 하면 orderProduct를 알아서 JSON 형식으로 전성이 가능하다 await를 통해서 비동식형식을 받는 것이다.
`${process.env.REACT_APP_API_BASE_URL}/order/create`,위 와 같이 작성을 백엔드로 보내는 것을 명시적으로 나타내고 post형시그로 보낸다는 것을 알수있다. 또한 JWT토큰을 localStroage에 담아있는 것을 headers에 꼭 담아서 보내야 한다. 이후 무조건 axios는 try catch문을 사용해서 e시 생성할 alert나 라우터를 기입할 필요가 있다.
<axios interception>
먼저 axios 인스턴스를 다음과 같이 작성한다.
// Axios 인스턴스 생성
const axiosInstance = axios.create({
headers: {
'Content-Type': 'application/json',
},
});
이후 아래와 같이 1번째 콜백은 정상작동 로직을 작성한다.
// Request용 인터셉터 설정
// 인터셉터의 use함수는 매개값을 2개 받습니다. 둘 다 콜백 함수로 전달합니다.
// 1번째 콜백에는 정상 동작 로직을 작성
// 2번째 콜백에는 과정 중 에러가 발생할 경우 실행할 로직을 작성.
axiosInstance.interceptors.request.use(
(config) => {
// 요청 보내기 전에 일괄 처리해야 할 내용을 콜백 함수로 전달.
const token = localStorage.getItem('ACCESS_TOKEN');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
console.log(error);
Promise.reject(error);
},
);
정상동작 로직을 작성한 이후에 콜백함수를 한개 더 불러와서 실패시 어떻게 처리할지를 작성한다.
.
// Response용 인터셉터 설정
axiosInstance.interceptors.response.use(
(response) => response, // 응답에 문제 없으면 그대로 응답 객체 리턴.
async (error) => {
console.log('response interceptor 동작함! 응답에 문제가 발생!');
console.log(error);
// 응답이 실패했는데, 토큰 재발급이 필요하지 않은 상황 (로그인을 애초에 하지 않음)
// 밑에 로직이 실행되지 않도록 return
if (error.response.data.message === 'INVALID_AUTH') {
console.log('아예 로그인을 하지 않아서 재발급 요청 들어갈 수 없음!');
return Promise.reject(error);
}
// 원본 요청의 정보를 기억해 놓자 -> 새 토큰 발급 받아서 다시 보내야 되니까.
const originalRequest = error.config;
// 토큰 재발급 로직 작성
// _retry의 값이 true라면 if문을 실행하지 마! -> 아까 했자나!
if (error.response.status === 401 && !originalRequest._retry) {
console.log('응답상태 401 발생! 토큰 재발급 필요!');
// _retry 속성은 사용자 정의 속성입니다. 최초 요청에서는 존재하지 않습니다.
// 만약 재요청 시에도 문제가 발생했다면 (refresh 만료 등),
// 더 이상 똑같은 요청을 반복해서 무한 루프에 빠지지 않도록
// 막아주는 역할을 합니다.
originalRequest._retry = true;
try {
const id = localStorage.getItem('USER_ID');
console.log(id);
const res = await axios.post(
`${process.env.REACT_APP_API_BASE_URL}/user/refresh`,
{ id },
);
const token = res.data.result.token; // axios는 json() 안씁니다.
localStorage.setItem('ACCESS_TOKEN', token); // 동일한 이름으로 토큰 담기 (덮어씀)
// 실패한 원본 요청 정보에서 Authorization의 값을 새 토큰으로 바꿔놓자.
originalRequest.headers.Authorization = `Bearer ${token}`;
// axios 인스턴스의 기본 header Authorization도 새 토큰으로 바꿔놓자.
axiosInstance.defaults.headers.Authorization = `Bearer ${token}`;
// axiosInstance를 사용하여 다시 한 번 원본 요청을 보내고, 응답값은 원래 호출한 곳으로 리턴.
return axiosInstance(originalRequest);
} catch (e) {
console.log(e);
// Refresh 토큰도 만료가 된 상황 (로그아웃이 된 것처럼 보여줘야 함.)
localStorage.removeItem('ACCESS_TOKEN');
}
}
// 재발급 요청도 거절당하면 인스턴스를 호출한 곳으로 에러 정보를 리턴.
return Promise.reject(error);
여기가 길수밖에 없는 이유는 이것을 만들기 위한 작업이기 때문이다. 토큰의 기간이 만료되었다면 리액트와 스프링이 소통하면서 토큰을 새로운 토큰으로 업데이트하는 것이다. 따라서 로그인상태가 기간 이상으로 유지가 가능하게 되며 토큰이 탈취되더라도 일정시간 이후에는 만료되어 사용이 제한된다. 그러므로 보안성이 강화되는 측면이 존재한다.
'PlayData 백엔드 부트캠프 정리' 카테고리의 다른 글
| 쿠버네티스 활용 (서비스) (0) | 2024.12.12 |
|---|---|
| 쿠버네티스 활용 및 사용 (2) | 2024.12.11 |
| 다시 시작하는 부트 캠프 하루 후기 9일차 (0) | 2024.11.06 |
| 다시 시작하는 부트 캠프 하루 후기 7일차 (0) | 2024.11.05 |
| 다시 시작하는 부트 캠프 하루 후기 6일차 (1) | 2024.10.31 |