인프런 워밍업 클럽 스터디 1기 FE | 미션3,4,5 발자국

인프런 워밍업 클럽 스터디 1기 FE | 미션3,4,5 발자국

 

미션3: 퀴즈 앱

 

image

 

문제은행 형식으로 랜덤으로 출제되는 퀴즈를 풀 수 있는 앱

📇 레포지토리 주소: https://github.com/zldnlto/inflearn-warming-up/tree/main/03_%ED%80%B4%EC%A6%88%20%EC%95%B1

 

주요 로직 (기능 구현) : 함수 설명

문제와 정답은 아래와 같은 데이터 구조로 설계하여, 배열에 오답 리스트를 몇개 넣느냐에 따라 선택지 개수가 변화할 수 있게끔 하였다.

    {
      "question": "2+2",
      "correct_answer": "4",
      "incorrect_answers": ["8"]
    },

문제 데이터 data.json 을 불러와서 사용한다. 정답, 오답 선택 시 각각 correct, incorrect 클래스를 추가하여 css를 제어한다.

  • activeRestartBtn : nextBtn 을 hidden 처리하고, restartBtn 을 표시한다. lifePoint의 이모지를 해골로 나타내 남은 도전횟수가 없음을 표시한다.

  • drawLifePointEmoji : 인자로 LP(LIFE POINT)를 받아 남은 도전 횟수를 나타내는 하트 이모지를 렌더링한다.

  • selectRandomItem : 인자로 배열을 받아 랜덤으로 요소를 하나 뽑아 리턴한다.

  • disPlayQuizData : data를 인자로 받고 selectRandomItem을 통해 랜덤으로 생성한 퀴즈 아이템을 화면에 뿌려준다.

  • addAnswerBtn : quizItem의 “correct_answer”와 “incorrect_answers” 를 통해 배열을 생성해서 선택지 버튼들을 추가한다. 수학 문제는 정답이 무조건 하나인 점을 고려해 배열을 생성할때 배열의 첫 인덱스는 정답, 나머지는 오답으로 구성되었다. shuffle 함수를 통해 생성된 선택지 버튼이 랜덤으로 섞여 나타나도록 하였다.

  • handleNextBtn :NextBtn를 클릭하면 발생하는 이벤트 함수이다. 정답, 오답 선택에 따라 생성된 correct, incorrect 클래스들을 리셋하고, 다음 문제를 불러온다. 아무것도 선택하지 않고 NextBtn을 클릭 시에도 LIFE POINT가 감소한다.

 

function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    // 무작위로 index 값 생성 (0 이상 i 미만)
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

 

느낀점

데이터의 변화와 렌더링의 시점을 생각하는 부분이 취약하다는 걸 느꼈다. 데이터 → 렌더링 측면에서 [2번 이상 오답을 고르면 실패하는 기능]을 깔끔하게 구현하는 것이 이 과제중에서는 제일 어려웠던 것 같다. 차근차근 하다보면 어렵지 않은 부분인데 정신 안 잡으면 말린다고 해야하나 ,,ㅎ 데이터의 흐름 → 어떻게 화면에 그릴지 이 부분을 논리적으로 생각해서 코드를 짤 필요가 있는 듯하다.

명확하고 깔끔하게 사고하는 습관을 잘 들여서 렌더링 밀림이나 잔오류 없이 한번에 구현할 수 있게 되고 싶다. 잘 짠 남의 코드를 보고 배우자 ,,

Restart시에 화면 깜빡임 이슈가 있었는데 Restart란 어차피 초기화를 하는 것이 아닌가~ 하고 init() 함수의 재사용을 꾀하다가 생긴 문제였다. 페칭한 데이터를 변수에 담아두고 재사용하니 간단하게 해결되었다.

함수의 재사용도 좋지만 굳이 따지자면 데이터 호출을 최소화하는것이 좀 더 중요하다. 여러모로 신경써야겠다.

 



미션4: 책 리스트 앱

 

image

책 리스트를 추가하고 삭제할 수 있는 앱

📇 레포지토리 주소: https://github.com/zldnlto/inflearn-warming-up/tree/main/04_%EC%B1%85%20%EB%A6%AC%EC%8A%A4%ED%8A%B8%20%EC%95%B1

 

    {
      "question": "2+2",
      "correct_answer": "4",
      "incorrect_answers": ["8"]
    },

 

주요 로직 (기능 구현) Or 함수 설명

무난하게 진행했던 과제이다. 주요 함수 or 신경썼던 부분을 발자국으로 정리해본다.

  1. 객체배열 BOOK_LIST 를생성하여 책 데이터를 관리하고 있다.

const BOOK_LIST = [{id = "",title: "책제목", author: "김작가" }]

  1. ‘명확한’ 데이터 삭제를 위한 id값 생성

id는 난수 생성 후 작가이름+책제목+난수 형식으로 붙여주었다. 난수만 붙이면 될 수도 있는데 작가이름+책제목은 왠지 나중에 sort할때 도움되지 않을까 싶은,, 데이터에 대한 나름의 배려이다 😄 React를 쓸 때는 uuid 라이브러리를 썼는데 깊은 고민은 아니지만 어떻게 key를 생성해야 안전할까에 대한 고민을 했다. 그 결과 작가이름+책제목 조합부터 중복은 잘 없을텐데 거기다가 난수를 붙여주면 좀 더 안전할 것 같다는 결론..

{id = ""title: "책제목", author: "김작가" }

const generateRandomString = () => {
  return Math.random().toString().split(".")[1].substring(0, 8);
};

const addBookItem = (data) => {
  data.id = `${data.title}${data.author}${generateRandomString()}`;
  const bookItem = generateBookListItem(data);
  bookListItems.append(bookItem);
  BOOK_LIST.push({ ...data });
};
  1. setTimeout 사용하여 notice 구현

const noticeTimeout = (msg) => {
  notice.classList.add("active");
  notice.innerText = msg;
  setTimeout(() => {
    notice.classList.remove("active");
  }, 2000);
};

setTimeout으로 구현했는데 작은 문제가 있다. 2000ms 이내 또 하나의 책이 추가되거나 하는 경우 요소가 겹치는 문제가 생겨서 이미 생성된 노드에 관한 중복 처리가 필요하다.

 

아쉬운 점 / 느낀점 :

바닐라 JS로 과제 프로젝트를 4회정도 하니 느낀 개인적이고 작은 인사이트

element.className = "className";
element.classList.add("className");

이 둘을 어떻게 구분해서 써야할까? 싶었는데 프로젝트를 몇 번 거치며 사용해본 결과 className = ""은 DOM을 생성하면서 곧바로 클래스를 붙여줄 때, classList.add()는 active나 hidden 등의 클래스 추가로 화면을 조작할 때 사용하는 식으로 구분해주면 가독성 측면에서 용이할 것 같다.

단순 랜덤 추출이 아닌, 뽑힌 문제를 배열에서 제거하여 문제를 모두 풀 수 있는 기능을 추가해보고 싶다.



미션5: GitHub Finder 앱

image

GitHub API (Octokit) 을 사용한 깃허브 유저 검색 앱 프로필과 최신 레포지토리를 최대 5개 보여준다.

📇 레포지토리 주소: https://github.com/zldnlto/inflearn-warming-up/tree/main/05_Github%20Finder

 

디바운싱 기능 구현

  • 스로틀링과 디바운싱 정리 (GPT) → 둘 다 이벤트 핸들러의 호출 빈도를 제한한다.

    • 스로틀링: 연달아 발생하는 이벤트를 일정 주기로 제한한다. 일정 시간 간격으로 이벤트 핸들러가 실행됨 첫 번째 호출만 허용되며, 이후 호출은 지정된 시간동안 무시된다. 이벤트 핸들러의 호출 빈도를 제한하는 것으로, 예를 들어 스크롤 이벤트나 리사이즈 이벤트와 같은 주기적으로 발생하는 이벤트를 다룰 때 유용하다.

    • 디바운싱: 이벤트 발생 후 일정 시간이 지났을 때 이벤트 핸들러가 실행된다. 연이어 발생하는 이벤트의 호출을 제어하는 것으로, 검색 창과 같이 사용자가 연속해서 입력하는 경우에 유용하다. 사용자가 연속해서 타이핑할 때, 마지막 이벤트가 발생한 후 일정 시간이 경과한 후에 비로소 검색을 실행하도록 할 수 있음.

모듈화

  • script.js 에 코드를 쭉 짜보았는데, 사이즈가 생각보다 있어 모듈화를 진행해보았다. 폴더 구조는 아래와 같고 200줄 넘어가던 한 코드를 기능별로 분리해놓으니 전반적으로 상당히 가독성이 향상되었다고 느꼈다.
    메인 코드 script.js 은 확실히 깔끔해진 모습이다.

import { GITHUB_TOKEN } from "./config.js";
import { Octokit } from "https://esm.sh/@octokit/core";
import debounce from "./utils/debounce.js";
import { findUserInfo } from "./userSearch/findUserInfo.js";
import { findUserRepoInfo } from "./userSearch/findUserRepoInfo.js.js";
import { activeNotFoundNotice } from "./userSearch/activeNotFoundNotice.js";
import {
  createUserProfileImg,
  displayUserInfo,
  displayUserRepos,
} from "./userSearch/display.js";

// ... DOM 코드 생략 ... 

const debouncedHandleSearchInput = debounce(async (e) => {
  const value = e.target.value;

  if (value === "") {
    activeNotFoundNotice("");
    userCard.innerHTML = "";
  }
  if (value.length <= 2) {
    return;
  }
  try {
    const userData = await findUserInfo(value);

    createUserProfileImg(userData);
    displayUserInfo(userData);

    const latestRepoArr = await findUserRepoInfo(userData.login);
    displayUserRepos(latestRepoArr);
  } catch (error) {
    console.error("ERROR");
  }
}, 800);

searchInput.addEventListener("keyup", debouncedHandleSearchInput);

 

image

 

소소한 트러블슈팅 (렌더링 밀림)

기능 구현 중, 의도와는 다르게 반응이 한 글자씩 밀리는 에러가 있었다. (ex. 'zldnlto'를 입력하면 'zldnlto'의 검색 결과가 나와야 하는데 마지막 글자가 누락된 'zldnlt'의 결과가 나옴)

이벤트 핸들러를 keydown이벤트에 붙여서 이런 결과가 생긴 것인데, 이 경우 입력 필드의 값이 변경되기 전에 업데이트가 된다. 키가 놓이면 그 때 입력된 값을 인식하도록 keyup으로 변경해주니 의도한 대로 동작한다.

 

신경쓰이는 버그 🐛

실시간 검색 기능이어서 키 입력 발생때마다 검색이 진행되어 화면 깜빡임 오류가 있음! 유저 있음 -> 유저 없음 -> 유저 있음 상태로 진행될때 레포지토리 UI가 출력되며 깜빡이는데 추후 개선해보도록 한다. (프로필이 있어야 해당 레포지토리도 보이도록 제어해야 함)

 

느낀점

전체적으로 돌아봤을때 어디서 시간이 많이 걸린건지 했는데 → 기능 구현중에는 에러 핸들링이 가장 문제였다. 유저를 찾을 수 없으면 notice UI를 띄워야 했는데, if문으로 respose.status === 404 인 상황에 UI변화를 걸어주고자 했으나 if문에 걸리지 않고 콘솔 에러만 떠서 중간에 난항을 겪었다. 결과적으로 octokit공식문서 보고 해결했음. (ㅎㅎ..!) error.status 로 조건문을 잡아줘야 한다.
항상 데이터 페칭에서 헤매는 것 같은데,, 데이터 페칭 넘어가면 유독 코드가 실제 이해도를 반영하는 것 같다는 생각을 했다. 결국엔 데이터를 받아서 화면에 어떻게 매끄럽게 표현할 것인가인데 오픈소스들 찾아보면서 여러 코드를 접하고 분석해볼 필요가 있겠다. 명확히 설명하지 못하면 내 거 아니고 아는 거 아니다

일단 시작하는 것도 틀린 말은 아니지만 나의 경우엔,,, 생각을 하고 코드로 짜야지 에러가 덜 나는 것 같다.

  • 추후 추가할 기능 : 소소하지만 모듈화를 진행했으므로 script.js 를 index.js 로 이름을 바꾸는 것이 적합해보이고, 버그 수정해야함

댓글을 작성해보세요.

채널톡 아이콘