넘블챌린지 Stage6 회고록

JIWON LEE·2022년 10월 4일
0

넘블챌린지

목록 보기
2/2

넘블 챌린지 전체 회고를 따로 작성할 예정이니 6회차 내용만 간략하게 정리해보자.

주문서 페이지 작성 하기

이번 주 과제 ! (뷰단 캡쳐)




주의 사항

  • 구매자정보의 휴대폰 번호 변경 기능은 존재하지 않는다고 생각해주세요.
  • 받는사람정보의 배송요청사항 정보는 존재하지 않는다고 생각해주세요.
  • 배송지 선택 팝업은 수정, 삭제를 할 수 없고 선택만 할 수 있다고 생각해주세요.
  • 적용 가능한 할인 쿠폰은 항상 없다고 생각해주세요.
  • 결제정보의 결제방법은 쿠페이 머니, 휴대폰만 구현해주세요.

개발적 주의 사항

  • 주문서페이지는 보안이 중요합니다. 서버사이드렌더링을 통해 조회API를 클라이언트 사이드에서 호출하지 않도록 구현해보아요! Data Fetching: getServerSideProps | Next.js

이 내용은 팀회의를 하면서 주요하게 다루었던 주제이다. 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'가 해결되었다.
사실 이유는 정확히 파악하지 못했다.
렌더링과 관련한 이슈라고 판단...


  • 비즈니스 로직들을 적절히 모듈화해보아요! 인프콘 2022 다시보기Vanilla JS와 함께 지속가능한 프런트엔드 코드 만들기 - 인프런 수강바구니 개선기 세션을 참고해보아요

나머지 이슈사항은 크게 없다. 비즈니스 로직 모듈화는 5회차 코드와 유사한 방식으로 진행하였다.

이번 챌린지를 진행하면서 가장 많은 고민을 한 부분은 언제 로직 모듈화를 진행할 지 이다. 이에 대한 고민과 공부는 많이 필요할 것 같다. 프론트 개발자로서 언제 Hook을 생성할지, 폴더구조는 어떻게 가져가야할지 등등...


context api 사용..

이번 챌린지에서 처음 사용해본 것이 있다. 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 상태관리를 하였다. 모듈화는 아쉬운 부분이 많았지만 그래도 처음으로 상태관리의 필요성을 느끼고 사용하였다는데 의의를 두었다.

코드 참고

profile
포기잘하는 프론트엔드 개발자

0개의 댓글