원티드 프리온보딩 FE 1주차 팀프로젝트 회고 - 영화 검색 사이트

dev_sang·2022년 8월 17일
0

MyProjects

목록 보기
2/2
post-thumbnail

배포 페이지 링크
깃헙 레포지토리 링크

프로젝트 기능 구현 및 역할

  • 상단 검색창 및 검색창의 연관 검색어 드롭다운 UI 개발
  • 연관 검색어 드롭다운 창의 키보드 조작 기능 개발
  • 연관 검색어 추천 기능, 검색어와 일치하는 글자 굵게 표시 기능 개발

사용된 기술

  • JavaScript(ES6)
  • React.js
  • Sass
  • Git

라이브러리

  • classnames
  • axois
  • json-server
  • lodash

내가 구현한 부분

  • 여타 OTT 사이트들(넷플릭스, 왓챠..)의 검색창 UI를 참고하여 상단 검색 아이콘 클릭 시 검색창이 나오도록 했다.

  • onFocus, onBlur 이벤트와 상태값을 이용하여 검색 input에 포커싱될 때에만 검색창 드롭다운 창이 노출될 수 있도록 하였다.

  • 키보드 조작과 관련한 로직을 짜는 데에 고민을 많이 했었다. 여러 참고자료를 찾아본 결과 내가 이해 가능하고, 활용 가능하다고 판단되는 자료를 현 프로젝트에 맞게 수정하여 적용했다. 연관 검색어 배열의 index를 활용하여 키보드 조작이 가능하도록 구현했다.

  • 전역 상태로 관리되고 있는 검색 결과 값들을 가져와 검색하고 있는 글자(키워드)에 해당하는 영화 제목들을 필터링하는 로직을 구현했다. 아직 배우는 단계이기에 라이브러리를 사용하기보다 자바스크립트 기본 함수를 이용해 연관 검색어 기능을 구현해보고자 했다.


시연 영상

검색창 애니메이션


Header에 돋보기 아이콘 버튼 클릭 시 검색창 노출

검색 하단 드롭다운 창 (연관검색어)

  1. 검색어 입력 시 input이 포커싱 되고 검색 바 하단에 연관 검색어 드롭다운이 노출된다.
  2. 입력된 글자에 해당하는 영화 제목을 띄운다.
  3. 검색 단어의 글자와 일치하는 연관 검색어의 글자에 mark 처리
  4. 드롭다운 모달에서 키보드 방향키 (아래,위)로 연관 검색어 이동 가능
  5. Enter키로도 검색이 가능하다.

코드 살펴보기

들어가기 전에

  • 입력받는 검색어 : keyword

SearchForm.jsx

먼저 useDebounce 커스텀 훅을 입력받는 keyword에 적용해 debouncing 처리를 합니다.

그리고 handleSearchResult 함수에서 useSearch 커스텀 훅을 통해 debouncing 처리된 keyword (debouncedKeyword) 를 파라미터로 넣고 검색을 합니다. (이때 useSearch.jsx 는 비동기 처리로 데이터를 가져오는 부분 입니다.)

검색 결과 (response)는 setMovies 함수를 통해 리코일 전역 상태 값으로 업데이트 됩니다.

  const debouncedKeyword = useDebounce(keyword, 100);

  const handleSearchResult = async (event) => {
    event.preventDefault();
    useSearch(debouncedKeyword).then((response) => setMovies(response)); 
  };

영화 제목만 가져옵니다.
movies 는 위의 setMovies 함수를 통해 전역 상태로 업데이트되었던 그 전역 상태 값입니다.

  // 영화 제목만 가져오기
  const movieTitles = movies.map((movie) => movie?.title);

가져온 모든 영화제목 movieTitles 에서 입력받은 검색어 keyword와 일치하는 영화제목만을 가져오기위해 filter 매서드를 사용합니다. 필터링된 영화제목들은 filteredTitles 에 할당합니다.

  const filteredTitles = useMemo(
    (element) => {
      return movieTitles.filter((title) => title?.toLowerCase().startsWith(keyword?.toLowerCase()));
    },
    [keyword]
  );

filter 적용 코드 로직
- 먼저 모두 소문자로 바꾼다.
- 그 중 keyword의 앞 글자와 일치하는 영화 제목을 filteredTitles 에 할당한다.


키보드 조작 부분

연관 검색어 드롭다운에 들어갈 연관 검색어(영화제목)의 개수(length)에 index를 두어 onKeyDown 이벤트에 상향 방향키가 입력될 경우 -1을, 하향 방향키가 입력될 경우 +1을 합니다.

  const handleKeyDown = (event) => {
    if (event.key === 'Tab') return;
    if (event.key === 'ArrowUp') {
      event.preventDefault();
      currentIndex <= 0 ? setCurrentIndex(filteredTitles.length - 1) : setCurrentIndex((prev) => prev - 1);
    }
    if (event.key === 'ArrowDown') {
      event.preventDefault();
      currentIndex === filteredTitles.length - 1 ? setCurrentIndex(0) : setCurrentIndex((prev) => prev + 1);
    }

    if (event.key === 'Enter') {
      if (!filteredTitles[currentIndex]) return;
      setKeyword(filteredTitles[currentIndex]);
    }
  };

SearchForm.tsx의 리턴 부분
Dropdown컴포넌트로 filteredTitles(연관 검색어)를 넘겨준다.

  return (
    <>
      <form onSubmit={handleSearchResult} className={cx('search_form', { search_form_active: isSearchOpen })}>
        <input
          type='text'
          value={keyword}
          onChange={handleInputChange}
          onFocus={handleInputFocused}
          onBlur={handleInputBlur}
          onKeyDown={handleKeyDown}
          className='search_form_input'
          placeholder='검색어를 입력해주세요.'
        />
        <button type='submit' className='search_form_btn'>
          검색
        </button>
        <Dropdown filteredTitles={filteredTitles} />
      </form>
    </>
  );

이 컴포넌트에서는 DropdownItems컴포넌트로 연관 검색어(영화 제목)과 index를 넘겨준다.

Dropdown.jsx 코드 링크

이 컴포넌트에서 입력받은 검색어(keyword)의 글자와 일치하는 연관검색어(filteredTitles)의 글자에 mark처리를 한다.

DropdownItems.jsx 코드 링크

mark처리 로직 - markText 함수
keyword = 입력받은 검색어
title = 연관 검색어

입력받은 keyword와 연관 검색어가 될 title을 각각 split하여 한 글자씩으로 나눈다.
굵은 글씨 처리 할 텍스트는 배열 markStr에, 일반 텍스트는 배열 spanStr에 담는다.

forEach 매서드를 사용한다.

  • splitKeyword 와 title 에 toLowerCase 매서드를 적용하여 모두 소문자로 바꾼다.
  • 같은 글자가 있는 경우 markStr배열에 push한다.
  • 같은 글자가 아닌 경우 spanStr배열에 push한다.
  const markText = useMemo(() => {
    if (!keyword.length) {
      return <span />;
    }
    const splitKeyword = keyword.split('');
    const splitTitle = title.split('');

    const markStr = [];
    const spanStr = [];

    splitTitle.forEach((character, idx) => {
      if (!splitKeyword[idx]) {
        spanStr.push(character);
        return;
      }
      if (String(splitKeyword[idx]).toLowerCase() === title[idx].toLowerCase()) {
        markStr.push(character);
        return;
      }
      spanStr.push(character);
    });

느낀 점

잘한 부분

  • 키보드 이벤트 다루는 법을 처음 알았다. 구글링도 많이 하고 지인에게 물어보기도 하면서 배운 부분이 많았다.

  • 검색바 부분 SearchBar.jsx, 드롭다운부분 Dropdown.jsx, 드롭다운의 각 연관 검색어 DropdownItems.jsx 이렇게 컴포넌트를 잘 나누었다.

  • 연관 검색어에 같은 글자는 굵게 처리하는 법도 시행착오가 많았지만 잘 구현이 되어 다행이다.

아쉬운 부분

  • fuzzy matching을 적용하지 못했고 filter 매서드만을 사용해서 연관검색어를 구현했다. 다음에는 fuzzy matching방법도 적용해보고 싶다.

  • 각 컴포넌트에 들어가는 함수들은 따로 빼서 utils 디렉토리에 넣어두고 사용도록 리팩토링 하면 코드가 더 깔끔할 것 같다.

profile
There is no reason for not trying.

0개의 댓글