본문 바로가기
프론트엔드/Vue.js

로딩 화면 만들기

by step 1 2021. 9. 27.
반응형

bootstrap에 spinner 클래스를 이용

https://getbootstrap.com/docs/5.1/components/spinners/

 

Spinners

Indicate the loading state of a component or page with Bootstrap spinners, built entirely with HTML, CSS, and no JavaScript.

getbootstrap.com

 

movie.js

import axios from 'axios'
//  중복제거을 위해서 uniqBy를 사용
import _uniqBy from 'lodash/uniqBy'

export default {
  // module!
  namespaced: true,
  //data!
  state: () => ({
    movies: [],
    message: 'Search for the movie title!',
    loading: false
  }),
  // computed!
  getters: {
    // 실제 데이터를 계산해서 새로운 데이터 형식으로 반영할때 사용
    
  },
  // methods!
  // 변이: 관리하는 데이터를 변경시켜줄수 있다. 다른 메소드에서 변경할 수 없다.
  mutations: {
    // ['movies', 'message', 'loading']
    updateState(state, payload) {
      Object.keys(payload).forEach(key => {
        state[key] = payload[key]
        
      })
    },
    // assignMovies (state, Search){
    //   state.movies = Search
    // },
    resetMovies(state) {
      state.movies = []
    }
  },
  // 비동기로 동작한다.
  actions: {
    async searchMovies({state, commit}, payload) {

      // 영화검색을 동시에 동작하는 것을 방지
      if(state.loading) {
        return
      }

      commit('updateState', {
        message: '',
        loading: true
        // 검색된 imdbID 데이터의 중복을 제거
        // movies: _uniqBy(Search,'imdbID')
        // message: 'Hello world',
        // loading: true
      })
      try {
                // const { title, type, number, year } = payload
        // const OMDB_API_KEY = '7035c60c'
        // http -> https 로 변경
        //const res = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=1`);
        const res = await _fetchMovie({
          // 전개연산자 사용
          ...payload,
          page: 1
        })
        // console.log(res);
        const { Search, totalResults } = res.data
        commit('updateState', {
          // 검색된 imdbID 데이터의 중복을 제거
          movies: _uniqBy(Search,'imdbID')
          // message: 'Hello world',
          // loading: true
        })
        
        console.log(totalResults) // 261
        console.log(typeof totalResults) // string

        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 res = await axios.get(`https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`);
            const res = await _fetchMovie({
              ...payload,
              page: page
            })
            const {Search} = res.data
            commit('updateState', {
              // _uniqBy 를 이용해서 중복 제거
              movies: [
                ...state.movies,
                ..._uniqBy(Search, 'imdbID')
              ]
            })
          }
        }
      } catch (message) {
        commit('updateState', {
          // 초기화
          movies: [],
          // 메세지 출력
          message: message
        })
      } finally {
        commit('updateState', {
          loading: false
        })
      }
    }
  }
}

function _fetchMovie(payload) {
  const { title, type, year, page } = payload;
  const OMDB_API_KEY = '7035c60c';
  const url = `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}&s=${title}&type=${type}&y=${year}&page=${page}`;
  // const url = `https://www.omdbapi.com/?apikey=${OMDB_API_KEY}`;

  return new Promise((resolve, reject) => {
    axios.get(url)
      .then((res) => {
        // console.log(res)
        if (res.data.Error) {
          reject(res.data.Error)
        }
        resolve(res)
      })
      .catch((err) => {
        reject(err.message)
      })
  })
}

 

MovieList.vue

<template>
  <div class="container">
    <div
      :class="{'no-result': !movies.length}"
      class="inner">
      <div
        v-if="loading"
        class="spinner-border text-primary"></div>
      <div
        v-if="message"
        class="message">
        {{ message }}
      </div>
      <div
        v-else
        class="movies">
        <MovieItem
          v-for="movie in movies"
          :key="movie.imdbID"
          :movie="movie" />
      </div>
    </div> 
  </div>
</template>

<script>
import MovieItem from '~/components/MovieItem.vue'

export default {
  components: {
    MovieItem
  },
  computed: {
    movies(){
      return this.$store.state.movie.movies
    },
    message(){
      return this.$store.state.movie.message
    },
    loading() {
      return this.$store.state.movie.loading
    }
  }
}
</script>

<style lang="scss" scoped>
@import "~/scss/main.scss";

.container {
  margin-top: 30px;
  .inner {
    background-color: $gray-200;
    padding: 10px 0;
    border-radius: 4px;
    text-align: center;
    &.no-result {
      padding: 70px 0;
    }
  }
  .message {    
    color: $gray-400;
    font-size: 20px;
  }
  .movies {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
  }  
}
</style>

 

로딩화면을 확인하기 위해서 일부러 인터넷속도를 느리게 설정이 가능하다

개발자 도구에서 네트워크 탭을 들어가서 느린속도로 설정한다.

반응형