문제 상황
요구사항
디바운싱/쓰로틀링을 이용하여 실시간 검색 기능을 구현하세요.
데이터 관점에서의 서술
1. 사용자가 검색창에 값을 입력할 때마다 인지한다.
2. 인지한 값이 영화 제목이 포함되어 있는지 확인한다.
3. 인지한 값이 포함된 모든 영화 카드를 사용자에게 보여준다.
이 과정 안에서 API를 끊임없이 호출하게 되는데
무분별한 호출을 방지하고자 디바운싱을 사용하게 된다.
디바운싱과 쓰로틀링에 대한 개념을 알고 있을 리가 없는 나는 일단 디바운싱/쓰로틀링에 대한 개념부터 찾아봤다.
튜터님께서 제공해주신 자료를 참고하면서 검색 기능을 구현하려고 했다.
위의 로직을 차근차근 해결해 보자.
해결 과정
1. 사용자가 검색창에 값을 입력할 때마다 인지한다.
const $searchBox = document.querySelector(".movie-search-input");
$searchBox.addEventListener("keydown", realTimeSearch);
addEventListener을 이용하여 사용자가 입력한 값을 인지한다.
그동안 너무 많이 나와서 절대 까먹지 않을 것 같다.
사용자가 키보드를 눌렀을 때 실시간 검색 기능 함수 realTimeSearch를 실행시킨다.
const realTimeSearch = function (e) {
// 사용자가 입력한 값
const inputValue = $searchBox.value
.trim()
.toLowerCase()
.replace(specialSymbol, "")
.replace(/\s/g, "");
if (inputValue.length > 0) {
filterInput(inputValue);
}
};
이후에 사용자가 검색창에 입력한 값을 받는 변수 inputValue를 만들어 준다.
inputValue의 길이가 0을 초과할 때
즉, 사용자가 키보드에서 아무 키라도 눌렀을 때 영화 제목과 비교해 주는 함수의 인자로 넘긴다.
사용자가 입력한 값 뒤에 붙은 수많은 메서드들이 보이는가.
처음 코드를 작성할 때는 toLowerCase()만 존재하였다.
수많은 시행착오 끝에 붙은 메서드들인데 뒤에서 자세히 설명하겠다.
🚨 이렇게만 작성하면 사용자가 값을 입력할 때마다 filterInput 함수에서 API를 무분별하게 호출하는 문제가 발생한다.
✅ 그것을 해결하기 위해 이곳에서 debouncing을 이용해 주면 된다.
debouncing이 무엇인지 간략하게 말해보자면,
무수한 event가 발생해도 마지막 event를 기준으로 일정 시간이 지나면,
event를 1번만 발생시키는 프로그래밍 기법이다.
debouncing을 이용하게 되면 사용자가 키보드를 여러 번 눌러도 바로바로 API를 호출하지 않게 된다.
사용자가 마지막에 키보드를 누른 event를 기준으로 1번의 API가 호출된다.
개념에 대해 알았으니 위의 코드에 debouncing을 적용시켜 보자!
// 디바운싱 함수
let timerId;
const debouncing = function (func, timeOut = 300) {
clearTimeout(timerId);
timerId = setTimeout(func, timeOut);
};
// 실시간 검색 기능
const realTimeSearch = function (e) {
document.querySelector(".banner").style.display = "none";
document.querySelector(".movie-list-title-text").style.display = "none";
// 사용자가 입력한 값
const inputValue = $searchBox.value
.toLowerCase()
.replace(specialSymbol, "")
.replace(/\s/g, "");
// 디바운싱
if (inputValue.length > 0) {
debouncing(() => filterInput(inputValue));
}
};
2. 인지한 값이 영화 제목이 포함되어 있는지 확인한다.
const specialSymbol = /[\{\}\[\]\/?.,;:|\)*~`ㅇㄴ!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
const filterInput = async function (inputValue) {
const allMovieList = await fetchAllMovies(inputValue);
const searchedMovieList = allMovieList.filter((movie) => {
// 영화 제목에 있는 특수문자와 공백 제거하기
const movieTitle = movie.title
.toLowerCase()
.replace(specialSymbol, "")
.replace(/\s/g, "");
return includeByCho(inputValue, movieTitle);
});
makeSearchedMovieCard(searchedMovieList);
};
사용자의 입력값을 인자로 받는다. API는 TMDB에서 제공해 주는 영화 검색용으로 사용하였다.
fiter 메서드를 사용해서 영화 제목 안에 입력값이 포함되어 있을 경우,
해당 영화 상세 정보가 담긴 배열은 영화 카드를 만들어주는 함수인 makeSearchedMovieCard의 인자로 넘겼다.
처음에는 사용자 입력값과 영화 제목에 toLowerCase 메서드만 사용했었다.
why? 맨 처음에는 Enter키로 영화 검색을 하는 기능을 구현했기에 다른 메서드들이 필요가 없었다.
🚨 문제는 그렇게 되면 사용자가 영화 제목을 정확하게 입력해야만 영화 카드가 뜨게 된다.

✅ 실시간으로 영화 카드가 뜨기 위해서는 사용자가 영화 제목을 띄어쓰기를 하지 않거나 특수 문자를 입력하지 않아도 뜨게 만들어야 한다.
그렇게 만들기 위해서 사용자 입력값과 영화 제목 뒤에 많은 메서드들이 붙었다.
replace 메서드를 이용하여 공백과 특수문자를 제거할 수 있다.

3. 인지한 값이 포함된 모든 영화 카드를 사용자에게 보여준다.
const makeSearchedMovieCard = function (searchedMovieList) {
document.querySelector(".movie-list").innerHTML = ""; // 기존에 있는 영화 카드 삭제하기
makeMovieCard(searchedMovieList);
};
여기서부터는 코드가 매우 간단해진다.
그동안 많은 영화 카드를 제작하지 않았는가.
다른 js 파일에 있는 영화 카드 제작 함수를 가져와서 해당 HTML 태그에 넣어주면 끝이다.
🚨 사용자 값을 인지하는 과정에서 문제를 하나 겪었다.
검색창에 값을 입력하면 실시간으로 영화 카드가 뜨긴 하는데, 수퍼에 대한 영화 카드는 바로 뜨지 않았다.
스페이스바를 누르면 그제야 수퍼 소닉에 대한 영화 카드가 나온다.

✅ event를 "keydown"에서 "input"으로 바꿔주니 해결이 됐다.
input은 실시간으로 입력값을 반영해야 할 때 사용하고,
keydown은 특정 키 입력을 감지할 때 사용한다고 한다.
전체 코드
// 영화 검색을 위한 API
import { fetchAllMovies } from "../api/search-api.js";
// 영화 카드 템플릿 생성 함수
import { makeMovieCard } from "./movie-card.js";
// HTML 태그
const $searchBox = document.querySelector(".movie-search-input");
// 전역 변수 - 특수 문자
const specialSymbol = /[\{\}\[\]\/?.,;:|\)*~`ㅇㄴ!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
//
// 디바운싱 함수
let timerId;
const debouncing = function (func, timeOut = 300) {
clearTimeout(timerId);
timerId = setTimeout(func, timeOut);
};
//
// 실시간 검색 기능
const realTimeSearch = function (e) {
document.querySelector(".banner").style.display = "none";
document.querySelector(".movie-list-title-text").style.display = "none";
// 사용자가 입력한 값
const inputValue = $searchBox.value
.trim()
.toLowerCase()
.replace(specialSymbol, "")
.replace(/\s/g, "");
// 디바운싱
if (inputValue.length > 0) {
debouncing(() => filterInput(inputValue));
}
};
$searchBox.addEventListener("input", realTimeSearch);
//
// 사용자가 입력한 값을 필터링하는 함수
const filterInput = async function (inputValue) {
const allMovieList = await fetchAllMovies(inputValue);
const searchedMovieList = allMovieList.filter((movie) => {
// 영화 제목에 있는 특수문자와 공백 제거하기
const movieTitle = movie.title
.trim()
.toLowerCase()
.replace(specialSymbol, "")
.replace(/\s/g, "");
return includeByCho(inputValue, movieTitle);
});
makeSearchedMovieCard(searchedMovieList);
};
//
// 검색된 영화 카드를 만들어 주는 함수
const makeSearchedMovieCard = function (searchedMovieList) {
document.querySelector(".movie-list").innerHTML = ""; // 기존에 있는 영화 카드 삭제하기
makeMovieCard(searchedMovieList);
};
결과
위의 코드를 모두 작성하면 실시간 검색이 가능해진다!
도전 기능까지 모두 구현하였다.

키워드를 길게 입력할 일이 없어서 몰랐는데,
키워드를 길게 작성할 때 해당 영화 카드가 뜨지 않는 오류를 발견하였다...
근데 신기한 건 '극장판 짱구는 못말려'를 검색할 때는 잘 뜨는데, 다른 긴 제목의 영화를 검색하면 뜨지 않는다.
초성 키워드 검색을 위해 적용했던 함수가 있는데 그것이 문제가 되는 것으로 예상된다.
프로젝트는 끝났지만.. 수정은 끝나지 않았다..~
'TroubleShooting' 카테고리의 다른 글
[메달 집계 관리] - 사용자가 입력한 모든 값을 배열에 어떻게 넣을까? (0) | 2025.01.22 |
---|---|
[영화 검색 사이트] - 영화 카드 사이를 누르면 오류 메시지가 뜬다?! (0) | 2025.01.17 |
[영화 검색 사이트] - 사용자가 저장한 북마크는 어떻게 보여줄까? (1) | 2025.01.17 |
[영화 검색 사이트] - Local Storage를 이용해서 북마크 삭제 기능 구현하기 (1) | 2025.01.17 |
[영화 검색 사이트] - Local Storage 영화 데이터 저장 방법 개선하기 (2) | 2025.01.16 |