PRGRMS 자바스크립트 스터디 3주차 회고

허상범·2022년 10월 20일
0

프로그래머스에서 진행하는 자바스크립트 스터디에 참여, 학습한 내용을 적은 글입니다.

참여 스터디: [17기] 프론트엔드 개발을 위한 자바스크립트(feat. VanillaJS), 2022.09 - 2022. 10 (4주)
https://school.programmers.co.kr/learn/courses/15244

스터디 전체 회고는 1편 스터디 회고에 작성했습니다.


3주차 미션

  • 3주차 미션은 1, 2주차 미션의 Todolist를 이어하는 것이 아닌 단독 미션이다.
  • 공연 정보를 제공하는 api를 이용해 공연 검색기를 만드는 것이 미션이다.
  • 입력창에 키워드를 입력하면 엔터를 치지 않아도 자동으로 검색 결과를 보여줘야 한다.
    • async, await 사용
    • 검색 히스토리를 로컬스토리지를 활용
    • debounce로 input 입력 시 불필요한 호출 제어하기 등

주요 미션 작성 내용

1. state 관리 고민

  • 요구사항을 따라가면서 만들다보니 SearchResult와 App 두 컴포넌트에 각각 공연 목록을 내부 state를 관리하게 되었다. 다시 한 번 데이터 흐름을 생각해보니 공연 목록를 내부에서 갖고 있을 필요가 없다고 생각이 되어서 state를 따로 갖지 않고 필요할 때마다 api 호출해서 바로 사용하는 방식으로 코드를 작성했다.
  • SearchHistory 에서 input입력으로 검색한 목록 관리는 localStorage로 하게되어 이것도 내부에 state를 따로 두지 않고 필요할 때마다 localStorage를 사용하도록 했다.

2. async, await

  • fetch 요청을 처리하는 함수를 유틸로 따로 만들고 검색결과를 업데이트하는 함수에서 유틸함수를 사용하는 방식으로 작성
// ./js/App.js
this.updateSearchResult = async (keyword) => {
  if (!keyword.length) return;

  const requestURL = `${SEARCH_API_END_POINT}?keyword=${keyword}`;
  const result = await request(requestURL);

  searchResult.render(result);
};

// ./js/utils.js
const request = async (url) => {
  try {
    const res = await fetch(url);
    if (!res.ok) {
      throw new Error('서버에서 준 응답이 올바르지 않습니다.');
    }
    return res.json();
  } catch (error) {
    console.log('일시적인 오류로 응답하지 않습니다. 다시 요청해주세요.');
  }
};

3. debounce 사용

  • setTimeout를 사용해 디바운스를 구현했다.
const debounce = (func, delay) => {
  let timer;

  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => func(...args), delay);
  };
};

  • 검색창에 입력할 때 특정 시간 동안 입력이 없는 경우 api를 호출
  • 검색 결과를 렌더링하는 3가지 경우 중 하나의 경우에만 디바운스가 필요했음
// ./js/App.js

const searchInput = new SearchInput({
  // input 입력시에만 디바운스 적용
  handleKeyUp: debounce((keyword) => {
    this.updateSearchResult(keyword);
    searchHistory.setState(keyword);
  }, 300),
});

const searchHistory = new SearchHistory({
  handleHistoryClick: (keyword) => {
    this.updateSearchResult(keyword);
    searchInput.render(keyword);
  },
});

const searchResult = new SearchResult({
  handleMusicianClick: (keyword) => {
    this.updateSearchResult(keyword);
    searchInput.render(keyword);
  },
});

주요 피드백 & 개선

1. setState 책임 분리

  • 해당 코드는 SearchHistory 컴포넌트로 검색 기록 데이터를 로컬스토리지로 관리한다.
  • setState의 역할은 인자로 받는 state를 검증하고 변경, 렌더함수 호출인데 현재 내 코드는 키워드를 받아서 state를 만드는 일까지 하고 있었다. setState를 호출하는 곳에서 nextState를 만들어서 호출하는 형태로 변경을 해야 한다.

수정 된 코드

  • 키워드를 가지고 nextState를 반환하는 역할을 함수로 분리했다.
  • 이러게 분리하니 state 관련 형식 등이 변경될 때 setState 함수는 수정하지 않아도 되는 이점이 생긴 것 같다.
// SearchHistory.js
//...
this.generateState = (searchKeyword) => {
  if (!searchKeyword.length) return;

  const addedHistory = [...new Set([...this.getState(), searchKeyword])];
  const nextState =
    addedHistory.length > 5 ? addedHistory.slice(1, 6) : addedHistory;

  return nextState;
};

this.setState = (nextState) => {
  this.validateState(nextState);
  setLocalStorage(STORAGE_KEY, nextState);
  this.render();
};
//...
// App.js
//...
const searchInput = new SearchInput({
  handleKeyUp: debounce((keyword) => {
    this.updateSearchResult(keyword);
    const newHistoryState = searchHistory.generateState(keyword);
    searchHistory.setState(newHistoryState);
  }, 300),
});

스터디 소감

promise를 다시 한 번 제대로 학습할 수 있는 시간이었다. 코드 리뷰에서 함수의 역할 분리에 관한 피드백을 자주 받았는데 코드를 작성할 때 신경쓴다고 해도 놓치는 것들이 있다. 단순히 신경쓰는게 아닌 단계적으로 확인할수 있는 루틴을 생각해봐야겠다.

0개의 댓글