넘블 챌린지 전체 회고를 따로 작성할 예정이니 6회차 내용만 간략하게 정리해보자.
이번 주 과제 ! (뷰단 캡쳐)
이 내용은 팀회의를 하면서 주요하게 다루었던 주제이다. SSR을 통해 조회 API를 클라이언트 사이드에서 호출하지 않는 것이 왜 보안에 중요한지에 대해 깊게 논의 해 볼 수 있었다. 개발을 하면서도 가장 큰 어려움을 겪은 부분이었다.
기존 API호출을 위해 모듈화 한 service 함수를 사용하였지만 계속해서 checkout 페이지에서 , getServerSideProps에서 data props로 잘 넘어오지 않는 이슈가 발생했다. 결국 이미 해결한 팀원의 도움을 받아서 해결했다.
SSR에서 cookie에서 토큰을 못읽는 이슈였는데 확실히 이번 과제를 진행하면서 SSR을 제대로 이해하지 못하고 코드를 작성하는데서 발생하는 이슈가 많았던 것 같다.
// checkout/[id].tsx
export async function getServerSideProps(context: any) {
const { id } = context.query;
const cookie = context.req?.headers.cookie;
return {
props: {
ordersheetId: id,
data: await CheckoutService.getOrderSheetInSSR(id, cookie),
},
};
}
// checkout.service.ts
async getOrderSheetInSSR(id: string, cookie: string) {
const accessToken = this.getAccessTokenFromCookie(cookie);
if (!accessToken) {
return;
}
const { data } = await HttpUtil.get(`/ordersheet/${id}`, {
...super.getAuthHeaders(accessToken),
});
return data;
}
SSR환경에서 적용해주기 위해서 getServerSideProps 함수에서 cookie를 먼저 읽고 모듈화한 getOrderSheetInSSR 함수에 전달해주어서 data를 받아왔다.
두번째로 구현에 어려움을 겪은 부분은 BroadcastChaneel과 postMessage를 이용해서 배송지 목록 팝업을 구현하는 것이다.
관련된 example코드를 찾는데 어려움을 겪었다. 구글링을 통한 예시 참고가 힘들어서 에러 대처도 힘들었던 것 같다..
우선 처음 React에서 new BroadcastChannel() 객체생성시 등장하는 이슈는 'BroadcastChannel is not defined' 였다.
해결법으로 'broadcast-channel'라이브러리를 사용했지만 이때는 로컬에서는 문제 없이 작동했지만
unhandledRejection: TypeError: PQueue is not a constructor
에러가 떴고 서버 배포시 서버에러(500error)가 발생했다.
이는 도저히 해결할 수가 없어서 테오프엔 톡방에 도움을 요청했지만
라이브러리나 노드 버전 관련 이슈라고 예측할 뿐이었다..
도움을 주신 맷돌님께 감사하다.
결국 해당 라이브러리를 삭제하고 기존 웹 API BroadcastChannel()를 사용하는 방식으로 다시 해결하였다.
// ReceiverInfo.tsx
useEffect(() => {
const listener = (e: any) => {
setData(e.data);
dispatch({ type: "CHANGE_ADDRESS", value: data.id });
};
new BroadcastChannel("addressBox").addEventListener("message", listener);
}, []);
기존 정의문을 useEffect안으로 옮겨주었더니 'BroadcastChannel is not defined'가 해결되었다.
사실 이유는 정확히 파악하지 못했다.
렌더링과 관련한 이슈라고 판단...
나머지 이슈사항은 크게 없다. 비즈니스 로직 모듈화는 5회차 코드와 유사한 방식으로 진행하였다.
이번 챌린지를 진행하면서 가장 많은 고민을 한 부분은 언제 로직 모듈화를 진행할 지 이다. 이에 대한 고민과 공부는 많이 필요할 것 같다. 프론트 개발자로서 언제 Hook을 생성할지, 폴더구조는 어떻게 가져가야할지 등등...
이번 챌린지에서 처음 사용해본 것이 있다. order compelete 요청시 request body가 필요하다.
body에 필요한 data들이 독립적인 컴포넌트에서 지정해주는 방식이어서 해당 data를 종합하기 위해서는 많은 상태관리 로직들이 props로 전달되어야 했다. 이는 props drilling이나 코드 복잡도를 높인다고 판단해서 전역으로 상태를 관리해줄 필요성을 느꼈다. 라이브러리를 사용하지 않고 react에서 제공하는 context api를 사용해서 해결하였다.
// OrderContext.tsx
const OrderStateContext = createContext<OrderRequestBodyType | null>(null);
const OrderDispatchContext = createContext<OrderDispatch | null>(null);
export const OrderProvide = ({
initialState,
children,
}: {
initialState: OrderRequestBodyType;
children: React.ReactNode;
}) => {
const [orderRequestBody, dispatch] = useOrder(initialState);
return (
<OrderStateContext.Provider value={orderRequestBody}>
<OrderDispatchContext.Provider value={dispatch}>
{children}
</OrderDispatchContext.Provider>
</OrderStateContext.Provider>
);
};
// checkout/[id].tsx
const initialState = {
ordersheetId: Number(ordersheetId),
addressId: data.address.id,
usedCash: 0,
payMethod: "coupaymoney",
usedCoupaymoney: data.coupayMoney,
};
return (
<CheckoutPageLayout>
<OrderProvide initialState={initialState}>
<BuyerInfo data={data.buyer} />
<ReceiverInfo initialData={data.address} />
<CheckoutInfo data={data} />
<CheckoutButton />
</OrderProvide>
</CheckoutPageLayout>
);
OrderContext에서 state와 dispatch의 context를 따로 생성해주고 Provider를 종합해서 따로 모듈화 하였다.
checkout/[id]페이지에서 초기값을 세팅하고 적용해서 하위 컴포넌트에서 state와 dispatch를 사용할 수 있게 하였다.
// OrderContext.tsx
export function useOrderState() {
const orderRequestBody = useContext(OrderStateContext);
if (!orderRequestBody) throw new Error("Cannot find SampleProvider"); // 유효하지 않을땐 에러를 발생
return orderRequestBody;
}
export function useOrderDispatch() {
const dispatch = useContext(OrderDispatchContext);
if (!dispatch) throw new Error("Cannot find SampleProvider"); // 유효하지 않을땐 에러를 발생
return dispatch;
}
OrderContext에서 state와 dispatch를 하위 컴포넌트에서 사용하기 쉽게 함수화 및 export 해주었다.
아! 생각해보니 useDispatch를 이용한 상태관리도 처음해보았다...
사실 예전에 redux(전역 상태관리 라이브러리)를 사용해본 경험이 먼저 있어서 dispatch와 action을 통해 관리하는 부분이 자연스레 이해가 되었다.
이 부분은 useOrder Hook을 통해 모듈화 하여 사용하였다.
// useOrder.tsx
// reducer
function reducer(state: OrderRequestBodyType, action: any) {
switch (action.type) {
case "CHANGE_ADDRESS":
return {
...state,
addressId: action.value,
};
case "CHANGE_USEDCASH":
return {
...state,
usedCash: action.value,
};
case "CHANGE_PAYMETHOD":
return {
...state,
payMethod: action.value,
...action.option,
};
default:
return state;
}
}
function useOrder(initialState: OrderRequestBodyType) {
// useReducer, state와 dispatch 반환
const [orderRequestBody, dispatch] = useReducer(reducer, initialState);
return [orderRequestBody, dispatch];
}
export default useOrder;
전체적으로 useReducer + Context api 를 통해서 Order request body 상태관리를 하였다. 모듈화는 아쉬운 부분이 많았지만 그래도 처음으로 상태관리의 필요성을 느끼고 사용하였다는데 의의를 두었다.