MovieCart

Untitled.mp4


✅ MovieCart란?

OMDB API를 이용해 영화 데이터를 받아온뒤 , 사용자들이 검색한 영화를 화면에 보여줄수있는 간단한 영화검색 사이트 입니다.

? 기술스택: Vue, vuex, webpack, scss

Skill Info
webpack webpack을 활용한 모듈 번들링
scss scss를 활용한 component단위 style 처리
vue component단위의 vue파일 생성
vue-router를 통한 SPA 디자인 패턴 적용
vuex vuex의 store 기능을 통한 상태 관리
store의 데이터 모듈은 movie,about으로 구성
eslint eslint를 통한 코드작성 규칙 설정
bable bable을 통한 크로스브라우징 준수

? 상태관리

vuex

  • vuex 를 사용해 필요한 데이터를 movie, about 데이터 모듈로 나눠 필요한 컴포넌트에 사용하고있는 구조 이다.
  • vuex를 사용해 상태관리를 진행하면 복잡하게 얽힌 컴포넌트간 데이터 통신이, 독립적인 store에 연결해 관리하면 되기때문에 편리해진다.

movie 데이터 모듈

import axios from 'axios' // http 통신 도와주는 모듈 
import _uniqBy from 'lodash/uniqBy'

const _defaultMessage = '찾고싶은 영화를 검색해 보세요!!'

export default{
  //모듈화 가능하다는 의미
  namespaced: true, 
  /*
    [ data 형식]
  */
  state: () => ({
    movies: [],
    message: _defaultMessage,
    loading: false,
    theMovie: {}
  }), 
  /*
    [computed 형식]
    state와 연결되기 위해 내부 함수는 매개변수로 state를 받아야 한다.
  */
  getters: {},
  
  /* 
    [methods 형식]
    데이터 수정은 mutations 에서만 가능 => 데이터 수정을 위한 메서드
    즉 mutations는 데이터 변경만을 위한 매서드, 그외 매서드는 actions에서 처리
  */
  mutations:{
    //state 의 데이터를 갱신하는 매서드
    updateState(state,payload){
      //새로운 배열데이터 생성
      Object.keys(payload).forEach(key => {
        state[key] = payload[key]
      })
    },
    resetMovies(state){
      state.movies = [],
      state.message = _defaultMessage,
      state.loading = false
    }
  }, 
  /*
    [methods 형식]
    actions는 기본적으로 비동기로 처리되는 매서드(state를 바로 받아서 처리하지는 못한다.)
    context는 state, getters, mutations를 활용할수 있는 내용 => context를 구조분해 해서 사용할 요소들만 불러온다. 예: {state,commit}
    payload는 searchMovies를 활용할때 인수로 들어온 특정한 데이터 활용
  */
  actions:{
    async searchMovies({state, commit}, payload){
      // state.loading 이 true일 경우 함수 실행 종료
      // 사용자가 searchMovies 함수가 동작중일때 여러번 실행하는 것을 방지
      if(state.loading){
        return
      }

      // 검색 시작되면 메시지 초기화
      // 에러 메시지가 있다면 movieList 컴포넌트에서 movieitem을 출력 안하기 때문에 초기화
      commit('updateState',{
        message:'',
        loading: true //로딩 시작
      })

      try{
        // payload로 전달받은 데이터를 채워 넣는다.
        const res = await _fetchMovie({
          ...payload, page: 1
        }) 
        const {Search, totalResults} = res.data
        //state에 데이터를 넣기 위함
        //commit으로 updateState 연결
        // Search는 omdb에서 받은 영화 정보들, totalResults는 영화 갯수(string 타입)
        // commit 안에 mutations을 작성할때 띄어쓰기 하나도 들어가면 안된다.
        commit('updateState', {
          movies: _uniqBy(Search, 'imdbID')
        })
        // totalResults를 10진법의 숫자로 변환
        const total = parseInt(totalResults, 10) // total => 영화 총 갯수
        // Math.ceil()는 올림 함수
        const pageLength = Math.ceil(total / 10) // pageLength =>  페이지 갯수 
        if(pageLength > 1){
          // page 반복문
          for(let page = 2; page <= pageLength; page += 1){
            // number 가 10일때 요청 x
            // number 가 20일때 요청 1번
            // number 가 30일때 요청 2번
            if(page > (payload.number / 10)){
              break
            }
            //추가 omdbapi 요청
            const res = await _fetchMovie({
              ...payload, page: page
            }) 
            const {Search} = res.data
            // 추가로 commit 
            // 기존에 state 에 있는 movies 값이 덮어씌워지지 않게  새로요청되는 Search 와 함께 구조분해후 새롭게 배열화
            commit('updateState', {
              movies: [...state.movies, ..._uniqBy(Search, 'imdbID')]
            })
          }
        }
      } catch(error){
        commit('updateState', {
          movies: [],
          message : error.message
        })
      } finally{
        commit('updateState',{
          //로딩 끝
          loading: false
        })
      }
    },
    async searchMovieWithId({state, commit}, payload){
      // state.loading 이 true일 경우 함수 실행 종료
      if(state.loading){
        return
      }

      // 로딩 시작
      commit('updateState',{
        theMovie: {},
        loading: true
      })
      
      const { id } = payload
      try{
        const res = await _fetchMovie({
          id: id
        })
        commit('updateState',{
          theMovie: res.data
        })
      } catch(error){
        commit('updateState',{
          theMovie: {}
        }) 
      } finally{
        commit('updateState', {
          loading: false
        })
      }
    }
  } 
}


async function _fetchMovie(payload){
  // netlify의 서버리스 함수로 payload(영화관련 데이터)를 보낸다
  // post 한 이후의 데이터를 반환 할수 있도록 return
  // netlify 호출시 앞에 . 을 붙혀야 한다.
  return await axios.post('/.netlify/functions/movie', payload)
}

? 서버리스 통신(Fass)

서버리스 함수 구조

  • 서버리스 함수란 서버가 존재하지 않는다는 뜻이 아니라 서버를 직접 관리할 필요가 없는 구조
  • 영화 검색 api 키를 netlify(AWS의 Lamda) 와 같은 호스팅 서버의 서버리스 함수를 통해 보관했다가 사용자에게 데이터를 넘긴다.
  • 사용자에게 넘어간 데이터에는 인증키가 없기때문에, 해커가 사용자의 컴퓨터를 해킹하더라도 api키 정보를 알아낼수 없다.

서버리스함수 movie.js

//[ node.js 동작환경 ]

//aixos를 받아온다.
const axios = require('axios')
// OMDB API 키 -> .env 파일에 환경변수로 선언됬다.
const OMDB_API_KEY = process.env.OMDB_API_KEY

exports.handler = async function(event){
  //event.body를 네트워크 통신을 통해 받을때 문자 데이터로 받기때문에 이를 객체형식으로 변환해야 한다 -> JSON.parse()
  const payload = JSON.parse(event.body)

  // payload 구조분해
  // search 컴포넌트의 aply()매서드를 통해 전달받은 데이터가 내부로 들어간다.
  const {title, type, year, page, id} = payload
  // url주소는 삼항연산자 를 통해 id 값이 있는 경우 와 없는 경우로 나눠 넣는다. 삼항연산자  ======>   (조건 ? 참일때실행 : 거짓일때실행)
  const url = id 
    ? `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&i=${id}` //id값이 있을때
    : `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}` //id값이 없을때
  //const url = `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}`

  try{
    const res = await axios.get(url)
    if(res.data.Error){
      return{
        statusCode: 400,
        body: res.data.Error
      }
    } else {
      return{
        statusCode:200,
        body: JSON.stringify(res.data)
      }
    }
  }catch(error){
    return{
      statusCode: error.response.status,
      body: error.message
    }
  }
}