본문 바로가기

배워서 따라하는 포플/영화 검색 사이트

영화 검색 - 기본 검색, 추가 요청, ID중복 제거, 리팩토링

영화 검색 - 기본 검색

import axios from 'axios'

export default {
  namespaced: true,
  state: () => ({
      movies: [],
      message: 'hello movie',
      loading: false
  }),
  getters: {},
  mutations: {
    updateState(state, payload) {
      // ['movies', 'message', 'loading']
      Object.keys(payload).forEach(key => {
        state[key] = payload[key]
      })
    },
    resetMovies(state) {
      state.movies = []
    }
  },
  actions: {
    async searchMovies({commit}, payload) {
      const { title, type, number, year } = payload
      const OMDB_API_KEY = '7035c60c'
      const result = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=1`)
      const {Search, totalResults} = result.data
      commit('updateState', {
        movies: Search
      })
    }
  }
}

store폴더 안의 movie.js의 코드입니다.

 

actions에는 지난 Search.vue파일의 apply()메소드 로직을 갖고 와서 가공했습니다.

actions는 비동기로 작동하기에 async를 추가합니다.

searchMovies함수에 context, payload 두개의 매개변수를 사용합니다. context는 위에 state, getters, mutations를 대표하는 내용입니다. payload는 searchMovies함수가 실행할 때 들어오는 데이터들을 지칭합니다.

 

비동기 요청한 결과가 result에 저장되며 Search, totalResults를 가져옵니다.

Search, totalResults는 위에 state의 movies에 담아줘야하지만, 데이터 변이는 mutations에서만 일어날 수 있도록 지난 시간에 배워서 mutations에 메소드를 만듭니다.

 

mutations의 updateState메소드에 매개변수는 state와 Search 두개 있습니다.

updateState와 actions에 commit을 통해 데이터가 갱신될 수 있습니다.

 

methods: {
    async apply() {
      // Search movies
      this.$store.dispatch('movie/searchMovies', {
        title: this.title,
        type: this.type,
        number: this.number,
        year: this.year
      })
    }
  }

Search.vue파일의 script부분인 methods입니다.

 

여기서 store에 저장된 movie모듈을 가져와서 사용할 겁니다.

Store중 actions의 함수는 .dispatch메소드를 사용합니다. dispatch메소드를 통해 movie모듈에 searchMovies함수를 실행하며 매개변수는 payload를 전달합니다.

여기서 전달한 payload는 object인 title, type, number, year입니다.

 

 computed: {
    movies() {
      return this.$store.state.movie.movies
    }
  }

MovieList.vue의 script부분 추가했습니다.

여기서 data대신 computed를 사용하는 이유는 movie.js에서 movie데이터는 처음엔 빈 배열이지만, 나중에 데이터를 반환하면 그 데이터들을 반응형을 유지한 상태에서 현재 MovieList.vue에서 나타나야 하니까요.

<MovieItem 
        v-for="movie in movies"
        :key="movie.imdbID"
        :movie="movie" />

MovieList.vue의 template부분입니다. :movie="movie"만 추가했습니다.

script의 computed에서 배열 데이터를 가져오니까 여기서는 v-for를 사용하며 하나씩 보여줍니다.

 

<template>
  <div>{{ movie.Title }}</div>
</template>

<script>
export default {
  props: {
    movie: {
      type: Object,
      default: () => ({})
    }
  }
}
</script>

MovieItems.vue입니다. 

 

영화 검색 - 추가 요청

지금 10개의 영화만 출력할 수 있습니다.

search부분 작업할 때 10개, 20개, 30개를 선택할 수 있겠금 작업을 해서 이 부분을 추가 요청을 해보겠습니다.

const total = parseInt(totalResults, 10)
const pageLength = Math.ceil(total / 10)

// 추가 요청
if(pageLength > 1){
    for(let page = 2; page <=pageLength; page += 1){
        if(page > (number / 10)) {
        break
        }
        const result = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`)
        const { Search } = result.data
        commit('updateState', {
        	movies: [...state.movies, ...Search]
        })
    }
}

movie.js의 actions에 searchMovies메소드에 추가 작업합니다.

totalResults는 총 몇개 영화인지 string으로 반환해줍니다.

그래서 먼저 형변환해주고 math.ceil를 통해 총 몇페이지를 반환하는지 계산해줍니다.(페이지당 10개)

 

그리고 페이지가 1개 넘으면, 즉 영화가 10개 넘으면 반복문을 통해 재요청을 합니다.

이때 api요청시 마지막에 page=${page} 방식으로 변경해줘야 합니다.

updateState에서 기존 movies는 존재합니다. 매번 기존에 있는 10개에 더 추가해서 10개를 보여주는 거라서 배열에 우선 state.movies 기존 데이터들을 보류후, 뒤에 Search 데이터를 push해줍니다.

 

searchMovies({state, commit}, payload){ } 

여기서 state를 사용했습니다. 

searchMovies메소드의 매개변수에 state를 추가해줘야 합니다.

 

영화 검색 - 영화 목록에서 중복 ID 제거

영화 목록에서 같은 id가 존재하는 경우가 예전에 있었습니다.

지금은 이미 수정했습니다만, 실무에서 비슷한 경우가 생길 수 있어서 작업해 놓겠습니다.

 

Lodash라이브러리를 사용합니다.

npm i lodash

위 명령어를 통해 lodash 라이브러리를 설치합니다.

 

import _uniqBy from 'lodash/uniqBy'

movie.js에서 uniqBy기능을 import합니다.

 

commit('updateState', {
	movies: _uniqBy(Search, 'imdbID')
})

actions의 searchMovies메소드에 해당 부분을 윗코드처럼 수정합니다.

두 군대 있습니다. pageLength에 추가 요청하는 부분에서도 수정해야 됩니다.

 

영화 검색 코드 리팩토링

영화가 검색 못했거나, api key가 작동안 되거나등 이유로 error를 생길 수 있습니다.

예외처리를 만들 생각이고 코드를 리팩토링을 하겠습니다.

function _fetchMovie(payload) {
  const { title, type, number, year } = payload
  const OMDB_API_KEY = '7035c60c'
  const url = `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=1`
  
  return new Promise((resolve, reject) => {
    axios.get(url)
      .then(result => {
        if(result.data.Error) {
          reject(result.data.Error)
        }
        resolve(result)
      })
      .catch(error => {
        reject(error.message)
      })
  })
}

우선 fetchMovie함수를 만듭니다. 좀 이따 예외처리할 때 사용할 예정입니다.

본 모듈에서만 사용될 것이니 앞에 _를 붙였습니다.

 

  actions: {
    async searchMovies({state, commit}, payload) {
      try {
        const { title, type, number, year } = payload
        const OMDB_API_KEY = '7035c60c'

        const result = await _fetchMovie({
          ...payload,
          page: 1
        })
        const {Search, totalResults} = result.data
        commit('updateState', {
          movies: _uniqBy(Search, 'imdbID')
        })

        const total = parseInt(totalResults, 10)
        const pageLength = Math.ceil(total / 10)

        // 추가 요청
        if(pageLength > 1){
          for(let page = 2; page <=pageLength; page += 1){
            if(page > (payload.number / 10)) {
              break
            }
            const result = await _fetchMovie({
              ...payload,
              page
            })
            const { Search } = result.data
            commit('updateState', {
              movies: [...state.movies, ..._uniqBy(Search, 'imdbID')]
            })
          }
        }
      } catch (message) {
        commit('updateState', {
          movies: [],
          message
        })
      }
    }
  }

actions부분에 try, catch 예외처리를 합니다.

 

<div class="message">
  {{ message }}
</div>

MovieList.vue파일에 위 내용을 추가합니다.

movie모듈에 error생길 시 해당 에러 메세지 내용을 반환할 수 있겠금 합니다.