[포스코x코딩온] 웹개발자 풀스택 과정 3차 개인프로젝트 - 회고록

배포링크 : https://www.mymovieapp.store

깃허브 : https://github.com/7jjin/My-Movie 


🔥JinCha

리액트를 배운지 2주가 지나고 1주일 간의 짧은 개인 프로젝트 시간을 가졌다. 리액트를 처음 배웠고, 소규모 프로젝트인 만큼 어떤 새로운 아이디어를 도출해 개발하는 것 보단 클론 코딩을 통해 지금 까지 배운 리액트를 적용해보자는 마인드가 강했다.

여러 사이트가 있었지만 최근 취미를 두고 있는 영화와 관련된 사이트를 찾아봤다. 그 중 왓챠가 디자인이 맘에 들었고, 전체 적인 틀은 그대로 따라 하되,  내가 원하는 기능을 몇개 넣어봤다. 

 

 

⚒️ 추가한 기능 (리액트X)

기능1 : 일별(주간) 박스오피스 정보 가져오기 + 포스터

처음 영화 사이트를 만드려고 할 때 API 하나에 모든 내용이 있을 줄 알았다. 

 

하지만...

A -> 영화진흥위원회 : 일별(주간) 박스오피스 정보는 있지만 포스터 이미지가 없음.
B -> 한국영화데이터베이스 : 포스터 이미지는 있지만 일별(주간) 박스오피스 정보는 없음.

 

두개의 API에 나눠져 있어서 처음부터 막막했다. A의 데이터를 가져와서 B의 API 요청 인자에 A에서 불러온 데이터 중 영화 제목과 개봉일을 추가시켜 포스터를 불러와야 했다. 모든 박스오피스의 정보를 map함수로 돌면서 포스터를 조회한다. 

 

const getData = async () => {
      const res = await axios({
        method: "GET",
        url: `https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=${SECRETKEY1}&targetDt=${now}`,
      });
      const boxOfficeMovies = res.data.boxOfficeResult.dailyBoxOfficeList;
      // 각 영화에 대해 getMovies 함수를 호출
      const moviePromises = boxOfficeMovies.map((movie) => getMovies(movie));
      const moviesWithPosters = await Promise.all(moviePromises);
      const updatedMovieList = boxOfficeMovies.map((movie, index) => ({
        ...movie,
        poster: moviesWithPosters[index],
      }));
      dispatch(TodayMovieChartAction.isLoading(updatedMovieList));
    };

    // 박스오피스 영화 데이터의 영화제목과 개봉일 정보를 인자로 받아와 포스터를 가져오는 함수
    const getMovies = async (movie) => {
      try {
        const json = await axios({
          method: "GET",
          url: `https://api.koreafilm.or.kr/openapi-data2/wisenut/search_api/search_json2.jsp?collection=kmdb_new2&detail=Y&title=${
            movie.movieNm
          }&releaseDts=${movie.openDt.replaceAll("-", "")}&ServiceKey=${SECREKEY2}`,
        });
        const posterURL = json.data.Data[0].Result[0].posters.split("|")[0];
        const title = json.data.Data[0].Result[0].title;
        return posterURL;
      } catch (error) {
        console.error("Error fetching poster:", error);
      }
    };

 

두개의 비동기 함수를 동시에 실행해야 했다. 하지만 그 각각의 값들은 서로의 함수에 영향을 끼치면 안됬다.

그래서 PromiseAll을 사용했다. 이번에 처음 사용하며 배웠다. 

(두개의 비동기 함수를 병렬로 실행시키고, 각 함수의 실행이 서로 영향을 미치지 않는다.) 

다음에 또 쓸 수 있는 기회가 있으면 좋겠다.

 


지금은 그냥 평범한 기능의 문제였다. 리액트적인 이슈는 지금부터 쓸 것이다.

 

⚒️ 추가한 기능 (리액트O)

 

1️⃣ 메인페이지 렌더링 시 박스오피스 로딩이 늦어지는 이슈 (Redux-Toolkit)

박스오피스를 가져올 때 마다 총 2번의 axios가 발생한다. 그래서 그런지 메인페이지를 렌더링 할 때 마다 박스오피스를 불러오는데 로딩이 오래걸렸다. 사용자 입장에서 굉장히 불편할 거 같았다.

 

그래서 ReduxToolkit을 사용해 메인페이지에 최초로 로딩 될 때 박스오피스의 정보를 store에 저장했다. 

Redux를 처음 사용해 봤는데 useState를 전역에서 사용할 수 있는 느낌이었다. useState는 해당 컴포넌트에서만 쓰이지만 Redux는 어디서슨 사용할 수 있다. 최고였다.👍

 

2️⃣ 다크모드 & 라이트모드 (ThemeProvider)

 

 

여러 css 라이브러리가 있지만, 나는 styledComponent를 사용했다. 컴포넌트마다 개별화 할 수 있다는게 좋았다 ( + 사용하기도 쉽다.)

styledComponent에는 ThemeProvider라는 기능이 있다. 

 

ThemeProver란 ? 
작동 방식은 Context API를 기반으로 이루어져 있다. ThemeProvider로 감싸진 자식 Component들은 ThemeProvider로 전달받은 theme를 props로 전달받아서 사용이 가능합니다.

 

다크모드를 하는데 너무 좋은 기능인 거 같다. 최상단 컴포넌트를 ThemeProver로 감싸면 하위 컴포넌트들에게 props로 스타일을 지정해 줄 수 있다. 

 

export default function MainPage() {
  const isDarkMode = useSelector((state) => state.app.isDarkMode);
  return (
    <>
      <ThemeProvider theme={isDarkMode ? darkTheme : lightTheme}>
        <GlobalStyles />
        <_MainPage>
          <Header />
          <Navbar />
          <Main />
        </_MainPage>
      </ThemeProvider>
    </>
  );
}

// 적용 방법
background-color: ${(props) => props.theme.swiperColor};

 

3️⃣ 회원가입 & 로그인 폼 (React-hook-form)

회원가입 & 로그인 페이지는 왓챠와 똑같이 해보고 싶었다. 

왓챠는 input에 입력될 때마다 조건과 비교하여 체킹 아이콘이 실시간으로 바뀌는 기능이었다.

 

🤔 문제1. form을 한번 전송한 이후 부터 React-hook-form이 적용된다.

열심히 기능을 다 만들었지만 폼을 한번 전송해야 적용이 되는 이슈가 있었다. 리더님과 고민을 많이 했지만 쉽게 떠오르지 않았다. 하지만 해결 방법을 알고 난 뒤 서로 허탈해 했다. 

React-hook-form은 기본적으로 폼을 한번 전송해야 적용되는 mode: onSubmit이다. 이걸 mode:onChange로 바꾸면 된다. 그러면 사용자 입력 즉시 useform이 적용된다. 

 

해결: useForm의 mode:onSubmit -> mode:onChange 변경

 

 

🤔 문제2. 하나의 input을 입력하면 모든 input에 체킹 아이콘이 생겼다.

 

useForm의 watch를 이용해서 해당 input란이 빈값일 때가 뭔지 찾아봤다. 그 결과 undefined가 나왔고 조건문을 주었다.

 

 {nameWatch === undefined ? (
                      ""
                    ) : errors.name ? (
                      <_inValidIcon></_inValidIcon>
                    ) : (
                      <_VliadIcon></_VliadIcon>
                    )}

 

이렇게 모든 input에 조건을 주니 위의 영상처럼 오류가 생겼다.  콘솔로 watch의 값을 찍어보고 모든 input의 watch값도 콘솔로 찍어보니 어디서는 undefined로 나오고 어디서는 빈 문자열이 나오는 것을 찾아냈다. 이후 조건문을 다시 주었다.

 

{nameWatch === undefined || nameWatch === "" ? (
                      ""
                    ) : errors.name ? (
                      <_inValidIcon></_inValidIcon>
                    ) : (
                      <_VliadIcon></_VliadIcon>
                    )}

 

watch의 값이 undefined이거나 빈문자열이면 체킹 아이콘이 생기도록 하였다. 

해결: 각각의 조건에 undefined와 빈문자열을 추가하자

 


🥲마치며..

이 외에도 React-Router를 사용해 라우터를 설정했고, useState, useEffect를 사용해 2주동안 배운 리액트 훅들을 최대한 활용 하려 했다. 프론트엔드 개발자로써 컴포넌트를 최대한 많이 나눌려 하고, 기능 부분도 디테일을 챙기려 해서 많은 페이지를 만들지는 못했다. (맞다.. 반응형도 했지) 

코드 또한 급하게 작성한 것이라 나중에 꼭 리팩토링 작업을 진행하면서 수정하고 싶다.🤙

 

그래도 첫 리액트 프로젝트인 점을 생각하면 많은 것을 배워 갔다고 생각한다. 다음엔 클론코딩이 아닌 진짜 내 사이트를 만들고 싶다. + (데이터 시각화도 해보고 싶다.)