project | Vaniila JS로 영화검색 구현 회고

녕녕·2023년 5월 18일
0

회고log🐾

목록 보기
7/18

Open API를 활용하여 async/await으로 비동기처리하는 SPA 웹 영화검색사이트를 만들었습니다.

🔎 사이트

Demo Site
Github Repository

🔎 구현기능

// 검색
  const handleSubmit = (e) => {
    e.preventDefault();

    // 키보드 gif 숨기기, 로딩 스피너 보이기, 검색결과 새로 출력
    beforeTypeEl.classList.add('hide');
    loadingEl && loadingEl.classList.add('show');

    // 검색 api 호출 및 렌더
    countPages = selectCountEl.value;
    searchData(searchInputEl.value, selectYearEl.value, countPages);
  };

  searchFormEl.addEventListener('submit', handleSubmit);
  • 영화 제목으로 검색할 수 있고, 검색결과 개수, 영화 개봉년도를 옵션 설정하여 검색할 수도 있습니다.
    • 검색 input을 포함하는 form에 submit 이벤트를 추가했습니다.
    • 이벤트 동작시, 기본 기능은 막고 import한 searchData 함수에 입력된 값을 인자로 넣습니다.
    • searchData 함수는 매개변수로 받은 값으로 api를 호출하고, response 값을 후처리하여 render 함수로 보내주는 역할을 합니다.
    • 이때 값들은 document.querySelector를 사용하여 선택한 요소에.value를 붙여서 값을 선택했습니다.
  • 검색결과 보기 전까지 로딩 애니메이션이 동작합니다.
    • 폼 제출 즉시 원래 있던 이미지(beforeTypeEl)를 숨기고, 로딩 요소를 보여줍니다.
    • 로딩은 searchData 함수에서 response 값을 받고, 렌더링 직전에 제거됩니다.

  • [JS] Vanilla JS로 SPA 라우터 구현

    • 검색결과에서 하나의 영화 포스터를 클릭하면, 영화 상세페이지로 이동합니다.
    • 검색페이지와 상세페이지로 이동시, 서버로 데이터가 전송되지 않고 URL 만 바뀌면서 내용을 보여주는 SPA(Single Page Applicaiton)을 pushState와 popState를 이용해 PJAX방식으로 구현했습니다.
    • 포스팅에 더 자세히 기록했습니다.
  • 영화 상세페이지에서는 고화질의 포스터를 출력합니다.

    • api로 받은 response 값에서 poster 부분을 선택합니다.
    • poster 값을 보면 다음과 같습니다.
      • https://m.media-amazon.com/images/M/MV5BYTdiOTIyZTQtNmQ1OS00NjZlLWIyMTgtYzk5Y2M3ZDVmMDk1XkEyXkFqcGdeQXVyMTAzMDg4NzU0._V1_SX300.jpg
    • replace 메서드를 사용하여 해상도를 변경했습니다.
${
	detailPoster !== 'N/A'
	? `<img class="detailPoster-img" src=${detailPoster.replace('SX300','SX450')} alt="${detailTitle} poster"/>`
	: `<div class="no-image"></div>`
}
  • js 모듈화를 통해 가독성과 유지보수에 용이하도록 했습니다.
    • src 폴더의 디렉토리 구조는 아래와 같습니다.
📦src
 ┣ 📂css
 ┃ ┃ ┣ 📜main.scss
 ┃ ┃ ┗ 📜reset-css.css
 ┣ 📂js
 ┃ ┃ ┣ 📜api.js
 ┃ ┃ ┗ 📜handlePushstate.js
 ┣ 📂pages
 ┃ ┣ 📂Detail
 ┃ ┃ ┣ 📜detailMarkup.js
 ┃ ┃ ┗ 📜index.js
 ┃ ┗ 📂Search
 ┃ ┃ ┗ 📜index.js
 ┃ ┃ ┣ 📜searchData.js
 ┃ ┃ ┣ 📜searchMarkup.js
 ┃ ┃ ┣ 📜searchResultsRender.js
 ┗ ┗ ┗ 📜searchYearOption.js
  • API 비동기 처리는 async, await을 사용했습니다.
    • 따로 api 함수 파일을 만들어서 필요할 때마다 import 해서 사용하였습니다.
// api.js

export async function getMovies(title, year, page) {
  try {
    const res = await fetch(
      `https://omdbapi.com/?apikey=7035c60c&s=${title}&page=${page}&y=${year}`
    );
    const json = await res.json();
    return json;
  } catch (error) {
    console.log(error);
  }
}
export async function getMovieDetails(movieId) {
  try {
    const res = await fetch(
      `https://omdbapi.com/?apikey=7035c60c&i=${movieId}&plot=full`
    );
    const json = await res.json();
    return json;
  } catch (error) {
    console.log(error);
  }
}


// searchData.js

import { getMovies } from '/src/js/api';

export const searchData = async (keyword, year, countPages) => {
  const moviesData = [];
  for (let i = 1; i <= countPages; i++) {
    const response = await getMovies(keyword, year, i);
    response.Response === 'True'
      ? moviesData.push(...response.Search)
      : moviesData.push(...[]);
  }
  searchResultsRender(moviesData);
};


// src/pages/Detail/index.js

import { getMovieDetails } from '/src/js/api';

const renderDetail = async (id = 'tt0114709') => {
    const response = await getMovieDetails(id);
    생략...
};

export default renderDetail;

🔎 알게된 것

제일 큰 수확은 아무래도 API를 사용해서 만든 첫 프로젝트이기에 비동기 함수 처리에 대해 알게 된 부분이 제일 큰 수확이었습니다. 더불어 처음으로 번들러를 적용해봤다는 것도 굉장히 의미있었습니다. 그 이외에는 아래와 같습니다.

  • HTML는 input 태그를 활용하며 유효성 검사하는 법, innerHTML을 활용하는 법, select option 태그의 value를 가져오는 법
  • CSS는 input cursor 크기를 조절하는 법과, CSS 요소의 import 방식.
  • Git은 branch 병합하는 것과 삭제하는 것, .gitignore.env
  • JavaScript는 나머지 매개변수, AJAX, SPA 라우터 구현, 커스텀 이벤트, DOMContentLoaded 이벤트, match 메서드, dataset 속성
  • bundler를 NPM, package.jsonpackage-lock.json의 역할 및 차이점, dependencies, SEMVER(Semantic Versioning)

알게된 것 중 더 자세히 알고 싶은 것에 대해서는 위에서도 언급하였듯이 포스팅으로 남겨뒀습니다.

🔎 해결한 것

⌨️ 무한스크롤 동작시, 페이지가 1개가 아니라 10개 이상이 증가

// 무한스크롤
const infinite = async () => {
  if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
    page += 1;
    getDataAndRender();
  }
};

window.addEventListener('scroll', infinite);
  • page 1개당 10개의 영화 목록이 추가됩니다. 위 코드로 동작하여 스크롤을 화면 제일 밑으로 내리면, 10개가 아니라 100개 이상이 추가됐습니다.
  • 무한스크롤 구현 방법을 여럿 찾아보니 debounce를 적용하지 않아서 이런 문제가 발생한다는 것을 알았습니다.
  • [JS] Vanilla JS로 라이브러리없이 무한스크롤 구현하기로 포스팅을 따로 해두기도 했는데, 포스팅처럼 debouncing을 해주니 page가 1만 증가하여 영화 목록이 10개만 추가됐습니다.

⌨️ typeError is not a function

  • 함수명에 오타가 있었고, 올바르게 수정하니 에러는 사라졌습니다.

⌨️ build error

몇시간 내내 씨름한 결과 package.json에 라이브러리들을 잘못 설치하고 scripts를 잘못 써서 그런 것이었습니다! 여러 에러들이 동시에 떴기에 당시에는 정확히 뭐가 문젠지 몰랐는데, 정리하면서 잘 알게 됐습니다.

  • script를 "build": "parcel index.html”에서 "build": "parcel build index.html"로 수정했습니다.
  • 타입스크립트를 사용하지 않기에 "@types/autoprefixer"를 삭제했습니다.
  • parcel 번들러를 사용한건데, devDependencies에"parcel"이 없었습니다. commit을 뒤져보니 다른 라이브러리 삭제 후 재설치하는 과정에서 삭제됐었기에 다시 설치했습니다.

제일 뿌듯하고 안도감 드는 빌드완료 문구!

그리고 자꾸 경고메시지가 떠서 찾아보니 현재 parcel은 autoprefixer 라이브러리를 별도로 설치할 필요가 없다고 합니다. package.json의 browserslist 설정에 따라 자동으로 해당 기능을 활성화하여, 자동으로 CSS에 벤더 프리픽스를 추가해준다고 했습니다.

  • npm uninstall autoprefixer도 진행해주었습니다.

🔎 회고

비동기 처리를 처음 해보아서 api를 호출하여 response값을 받아 render하고, 더보기 버튼으로 영화 목록을 더 불러오는 것만 구현하는 것도 어려웠다. 추가 작업을 진행할 때는 익숙해져있던 때라 크게 어렵지 않게 기능 구현했다. 다만 PJAX 방식의 SPA를 구현하는 것은 정말 어려웠다. 가장 오래걸렸던 작업이었던 것 같다.

  • 좋았거나 잘했다고 생각하는 점

    • 어려워도 늦어져도 느려도 포기하지 않고 완성해내서 좋다.
    • JavaScript Router 방식을 공부하며, 클라이언트 사이드 렌더링의 기본 Router 작동 방식을 이해하게 된 것 같다.
  • 아쉬웠기에 다음에 시도해볼 점

    • 오랜 시간에 걸쳐 조금씩 리팩토링을 진행하고 기능을 다듬었다. 그렇다보니 그 기간 다른 프로젝트에서 쓰고 있던 commit 규칙을 적용하느라, 기간마다 commit prefix를 다르게 적용했다. 혼자서 작업하더라도 일관되게 commit message를 쓰기 위해 wiki에 잘 적어두면 더 좋을 것 같다. pr 단위를 더 적게하면 나중에 관련 작업 내용을 볼 때 훨씬 수월하게 볼 수 있을 것 같다.
    • 좀더 밀도있게 몰입하고 집중하여 작업 기간을 줄였다면 좋았을 것 같다.
    • 완성하고 나니 생각보다 사이트의 완성도가 부족하다는 느낌을 받았다. 다른 프로젝트 리팩토링 후 시간이 된다면 로그인 기능, day&night 기능, 최근 검색어, 최근 본 영화, 좋아요 기능 등등을 추가로 작업하며 사이트를 더 멋지게 만들어나가보고 싶다.
profile
FE Developer | 차근차근

0개의 댓글