[Next.js] indexedDB로 최근 본 영화 구현하기

JunSeok·2022년 10월 12일
0

Movie-inner 프로젝트

목록 보기
2/13
post-thumbnail
post-custom-banner

썸네일 출처

Next.js를 이용하여 프로젝트를 진행하였습니다.

indexedDB는 웹 브라우저에 정보를 저장하는 툴 중 하나이며,
최근 본 작품 기능을 구현하기 위해 indexedDB를 사용하기로 결정했습니다.

왜 indexedDB?

웹 브라우저에 데이터를 저장하는 기술은 많습니다.
대표적으로 cookie, localstorage, indexedDB가 있습니다.

cookielocalStorageindexedDB
저장공간소량5MB브라우저마다 다름, chrome의 경우 컴퓨터의 저장장치의 80%까지 저장가능 (이런 정책은 언제든지 바뀔 수 있음)
네트워크에 포함포함됨(때문에 인증작업에 많이 쓰임)포함안됨포함안됨
데이터 타입문자열문자열제한없음!
동작 방법동기식동기식비동기식(동기식보다 빠르게 구현 가능)
난이도쉬움쉬움어려움!

생활코딩님이 비교해주신 내용입니다.

indexedDB를 선택한 이유는 다른 툴들과 비교하여 장점이 많고 특히 데이터 타입에 제한이 없다는 점이 가장 컸습니다.
다른 툴들을 사용하게 되면 또 json.parse()와 json.stringify를 사용해야 하기 때문입니다.
난이도가 어렵다고 하셔서 바로 시도해보았습니다.

구현

우선 순서는 다음과 같습니다

  1. open a database
  2. create a object store in database
  3. object store 내에서 CRUD

매우 간단합니다!

우선 Next.js에서 indexedDB를 사용하기 위해서는 useEffect 내에서 사용해야 합니다.
useEffect는 CSR 전용 hook이고 indexedDB 자체가 브라우저 내의 기능이기 때문에 useEffect 내에서 사용해야 Next.js는 이 동작이 브라우저 내에서 동작한다는 확신을 가지므로 에러가 발생하지 않습니다.

useEffect(() => {
  // open 메소드의 첫 번째 인자로 db 이름 넣어주고 두 번째 인자에는 버전을 적어줍니다. 
  // 첫 번째 버전이니 1을 넣어줍시다
	const previousWatchedReq = window.indexedDB.open('movieinfo', 1);
  
  // 이 변수는 DB에 접근할 수 있는 레퍼런스로서의 역할을 할 것입니다.
    let previousWatched;
  
  // 비동기작업이기 때문에 성공, 업그레이드 또는 에러가 발생했을 때 이를 제어할 객체를 요청해야 합니다.
  
  // 버전이 바뀔 때 실행되는 onupgradeneeded 이벤트에서 object store를 생성해줍니다.
  previousWatchedReq.onupgradeneeded = (e: any) => {
     previousWatched = e.target.result
    // objectStore 이름 적어준다, 그리고 keypath 값을 무엇으로 할 지 정하고 keypath 값이 자동으로 1씩 늘어나는 autoIncrement 설정을 해준다.
	 previousWatched.createObjectStore('watched', { keyPath: 'id', autoIncrement: true })
  }
  
  // onsuccess는 DB관련 작업 성공할 때마다 실행, 여기서는 콜백함수로 open이 성공했을 때 할 작업 실행
  previouseWatchedReq.onsuccess = (e) => {
  	// previouseWatched는 DB에 접근할 수 있는 레퍼런스의 역할을 받습니다.
   	previouseWatched = e.target.result 
    
    // 첫 번째 인자로 objectStore 이름 적어주고, 무슨 작업을 할 것인지 적어준다.
    // readonly는 DB 불러읽기이며, readwrite는 쓰기, 삭제를 할 수 있다.
    let store = previousWatched.transaction('watched', 'readonly').objectStore('watched')
  }
  
  // onerror는 DB관련 작업 실패 때마다 실행
  previousWatchedReq.onerror = (e: any) => {
    const error = e.target.error
    console.error('indexedDB error: ', error.name)
  }
},[])

사용법은 위와 같습니다.
모든 것을 적어놓기에는 양이 많아 아래에 제가 구현한 코드를 참고용으로 적어놓겠습니다!
코드가 번잡하지만 우선 구현에만 집중하였기에 양해 부탁드립니다..

구현 목표는 다음과 같습니다.

  1. 중복될 시, 앞에 항목 삭제하고 뒤에 넣는 방식으로 항목 업데이트하기
  2. 10개 이상 쌓이면 앞에서부터 하나씩 삭제하기
		useEffect(() => {
                // 데이터 베이스 생성
                const previousWatchedReq = window.indexedDB.open('movieinfo', 1)
                // DB에 접근할 수 있는 레퍼런스 생성
                let previousWatched 
                
                // // 시간이 걸리는 비동기작업이기 때문에 성공 또는 에러가 났을 때 제어할 객체를 요청해야 한다.

                // error는 DB관련 작업 실패 때마다 실행
                previousWatchedReq.onerror = (e: any) => {
                    const error = e.target.error
                    console.error('indexedDB error: ', error.name)
                }
                
                // 버전이 바뀔 때마다 실행되는 이벤트에서 object store 생성
                previousWatchedReq.onupgradeneeded = (e: any) => {
                    console.info('database upgrade success!')
                    previousWatched = e.target.result
                    previousWatched.createObjectStore('watched', { keyPath: 'id', autoIncrement: true })
                }
                
                // success는 DB관련 작업 성공할 때마다 실행, 콜백함수로 open이 성공했을 때 할 작업 실행
                previousWatchedReq.onsuccess = (e: any) => {
                    console.info('database open success!')
                    previousWatched = e.target.result
                    let store = previousWatched.transaction('watched', 'readonly').objectStore('watched')
                    let getAllList = store.getAll()
                    getAllList.onsuccess = (e) => {
                        const watchedMovieList = e.target.result
                        const movieIdList = watchedMovieList.map((arr) => arr.movieId)
                        // 추가할 항목 중복 체크, 중복되면 삭제
                        if (movieIdList.includes(movieId)) {
                            const movie = watchedMovieList.filter((arr) => arr.movieId === movieId)
                            let store = previousWatched.transaction('watched', 'readwrite').objectStore('watched')
                            let deleteMovie = store.delete(movie[0].id)
                            // 중복 삭제 성공하면 추가할 항목 추가
                            deleteMovie.onsuccess = (e) => {
                                let store = previousWatched.transaction('watched', 'readwrite').objectStore('watched')
                                let addReq = store.add({
                                    movieId: movieId,
                                    title: movieInfoBox.title,
                                    poster_path: movieInfoBox.poster_path,
                                })
                                addReq.onsucces = (e: any) => {
                                    toast.success('중복 삭제 후 추가 성공')
                                }
                            }
                        }
                        // 중복이 아닌 경우, 그냥 추가
                        else {
                            let store = previousWatched.transaction('watched', 'readwrite').objectStore('watched')
                            let addReq = store.add({
                                movieId: movieId,
                                title: movieInfoBox.title,
                                poster_path: movieInfoBox.poster_path,
                            })
                            addReq.onsucces = (e: any) => {
                                toast.success('추가 성공')
                            }
                        }
                        // 항목 10개 넘어가면 앞의 항목 하나씩 삭제
                        if (watchedMovieList.length > 10) {
                            let store = previousWatched.transaction('watched', 'readwrite').objectStore('watched')
                            const id = watchedMovieList[0].id
                            let deleteMovie = store.delete(id)
                            deleteMovie.onsuccess = (e) => {
                                toast.error('삭제 성공')
                            }
                        }
                    }
                }
    		}, [])

참고자료
mozilla
생활코딩

profile
최선을 다한다는 것은 할 수 있는 한 가장 핵심을 향한다는 것
post-custom-banner

0개의 댓글