TIL: 2022-05-25 React 입문 과제

김하연·2022년 5월 25일
0

TIL: Today I Leaned

목록 보기
15/26

Github 에서 보기


Week3. 리액트 입문 과제 - 내 일주일 평점 남기기


과제 목표

  • 컴포넌트와 리액트 요소를 다룰 수 있어요.
  • 이벤트를 관리할 수 있어요.
  • 라우팅을 할 수 있어요.

과제 미션

내 일주일 평점 남기기 완성 + 파이어베이스 or S3로 배포 두 가지를 모두 완수해야 합니다.
1) 메인 페이지

  • 일주일 평점 보여주기
    - 평점은 1~5까지 숫자 중 랜덤한 정수로 만들어주세요.
    - 각 요일 옆 삼각형 버튼을 누르면 요일 평점 남기기 페이지로 이동하기
    2) 평점 남기기 페이지
  • 선택한 요일 보여주기
  • 동그라미를 눌러서 평점 입력하기 (1번째 동그라미 누르면 1점, 3번째 동그라미 누르면 3점)
  • 남기기 버튼을 누르면 이전 페이지로 이동하기
    3) 평균 평점 보여주기 (메인 페이지 컴포넌트에 구성 or 하위 컴포넌트인 평균 평점 컴포넌트에 구성)
  • 각 요일별 랜덤 정수로 구성된 점수의 평균을 구하기
  • Reset 버튼을 누르면 평점 평균을 0으로 상테변화 시키기

+) 추가로 해보면 좋을 기능 (필수x)

  • 오늘을 기준으로 일주일을 보여주세요.
    • ex: 오늘이 화요일일 경우, "화-수-목-금-토-일-월"을,
      오늘이 목요일일 경우, "목-금-토-일-월-화-수"를 보여주세요.
  • 키보드의 숫자키를 눌러서 평점을 입력할 수 있게 해보세요. (input 사용X)
    • ex: 1,2,3,4,5 중 하나를 누르면 평점이 입력되도록 해보세요.


작업 내용

공통

  • CSS는 styled-componenets 패키지 활용
  • App.js 내에 메인페이지/상세페이지 두가지 컴포넌트로 나누어 진행

메인 페이지

  • for문을 사용하여 1~5까지의 숫자를 랜덤으로 생성
  • 랜덤 숫자의 평균값이 바뀔때마다 화면에 반영되도록 useState() 사용
  • 월~일 총 7개의 요소가 담긴 배열을 생성 후, new Data() 함수를 이용하여 오늘 요일에 대한 숫자를 반환받고
    오늘 요일을 기준으로 정수값이 나열된 새로운 배열 생성(App.js 파일 > dateFromToday 함수)
  • 요일 배열의 길이만큼 map 함수로 요일별 row 생성
  • 요일별 row 안에서 map 함수로 별 5개 생성
  • 각 요일에 해당하는 랜덤으로 생성된 평점 숫자를 map 함수로 돌아가는 별의 index와 비교하여 별 이미지 경로 변경(활성화/비활성화 이미지)
  • 평점 남기기 버튼에 연결된 요일별 상세 페이지는 파일 하나로 작업하고, 넘겨주는 파라미터 값에 따라 요일 정보가 나오도록 반영

상세 페이지(요일별 평점 남기기)

  • useState() 를 사용하여 클릭 및 키보드 이벤트가 발생할때마다 변경되는 별의 갯수를 반영
  • useEffect() 훅 안에 window에 keyup 이벤트를 추가하여 key값이 1~5일 경우 활성화된 별의 갯수 바뀌도록 반영
  • 삼항연산자를 활용하여 useState() 내에 선언된 별의 갯수인 count의 값이 변할 때마다 이모티콘이 바뀌어 보이도록 효과 추가
  • Array.from 함수를 활용하여 length: 5의 길이를 가진 새로운 배열을 생성해 map으로 버튼 5개 추가,
    해당 버튼이 눌리면 버튼의 index 값을 count의 새로운 값으로 지정해 선택된 별의 이미지 변경되도록 반영

사용된 Package

  • react-router-dom (v5.2.1)
  • styled-components

과제 마무리 후...

Redux 강의를 들으면 자식 컴포넌트에서 변경된 State값도 부모 컴포넌트에 반영되도록 할 수 있는 것 같은데, 모든걸 이해하고 진행하기엔 과제에 대한 부담이 있어서 우선 과제에 필요한 내용들부터 학습했다.
Redux가 데이터 저장소 같은 역할이라는데, 다음주차부터는 이 내용도 숙지해서 데이터가 업데이트되는 것에 대한 결과물도 만들 수 있지 않을까 하고 기대가 된다!

State같은 경우에는 처음에 강의만 보면서 잘 이해가 안됐는데, 이번 과제를 진행하면서 조금 더 개념을 잡을 수 있었던 것 같다.
데이터가 바뀔 경우에는 State를 사용하여 반영하고, 그 외에 고정값이 사용될때는 일반적인 변수에 담아서 사용하면 될 것 같다.

예를 들어 이번 프로젝트에서 별 5개를 만들 때 처음에 5라는 숫자를 별도 State로 저장해서 map으로 화면에 뿌려줄 생각을 했는데, 코드를 작성하다보니 별의 최대 갯수는 항상 5개인데 굳이 State로 만들어줄 필요가 있나? 하는 생각이 들었다. 그래서 우선 활성화되지 않은 별의 갯수인 5개는 임의로 5라는 length를 가진 배열로 만들어 map 함수로 이미지 5개를 생성했다.

이제 겨우 state나 react의 각종 기능에 대해 겉핥기 수준으로 파악하고 있는 상태겠지만.. 이번 과제를 통해 기초적으로 알아야 하는 부분은 어느정도 이해하고 넘어갈 수 있어서 좋았던 것 같다.

아, 그리고 useEffect안에서 console을 찍든 그 밖에서 찍든 컴포넌트가 update되거나 mount될 때 전부다 이벤트가 작동되길래 useEffect는 그럼 왜 필요할까?? 하는 생각이 들었다.
좀 더 알아보니 어차피 자바스크립트는 코드를 위에서부터 해석하고 내려오기 때문에 당연히 useEffect 안에 있든 밖에 있든 function안에 있는 모든 코드가 실행되는건 당연하지만, useEffect만의 차별점이 있다면 html이 모두 렌더링 된 후에 useEffect안의 내용이 실행되는 점이라고 한다.

함수의 핵심 기능 외에 쓸데없는 기능들을 프로그래밍 언어로 Side Effect라고 하는데, useEffect 라는 명칭 또한 거기서 따온거라고 한다.
그래서 컴포넌트의 핵심 기능은 html 렌더링이므로 그 외에 쓸데없는 기능들을 useEffect에 넣으라는 뜻이라는...
그러므로 시간이 오래걸리는 반복연산, 서버에서 데이터를 가져오는 작업, 타이머를 추가하는 등의 기능들은 useEffect 안에 많이 적는다고 한다.

그치만 Class형 컴포넌트에 존재하는 라이프사이클 메서드들을 대신하여 useEffect를 활용한다고도 했는데 이게 무슨 연관이 있을지는.. 좀 더 공부하고 연구해봐야 할 것 같다.


오늘 요일 기준으로 평점 리스트 생성 구현

오늘 요일을 기준으로, 에를 들면 오늘이 수요일이라면 수-목-금-토-일-월-화 순서로 평점 리스트가 보여지는 것이 추가 미션이었는데 알고리즘 주차에 비슷하게 구현 가능한 문제를 풀었던 기억이 스쳐지나가서 시도해보기로 했다!

우선 Date 객체를 활용해 getDay()로 오늘 요일에 해당하는 숫자값을 받아오고, ['일', '월', '화', '수', '목', '금', '토'] 의 요소를 가진 days라는 배열을 만들었다.
getDay() 메서드는 일요일부터 시작해 일요일은 0, 월요일은 1, 화요일은 2 이런식으로 반환값이 나오기 때문에 일요일부터 시작하도록 했다.
days 배열을 map으로 돌려 첫 번째 반환값을 오늘에 해당하는 숫자값이 되도록 했고, 그 후로는 1씩 플러스된 값을 반환하도록 했다. 그리고 반환할 값이 7이 되면 0으로 바꾸는 조건을 추가해 요일의 length를 넘어가지 않도록 했다.
그 결과 오늘이 수요일이라면 3부터 시작하는 [3, 4, 5, 6, 0, 1, 2] 라는 새로운 배열이 생성됐고, 새로운 배열을 key값으로 사용해 days의 요소들이 순서대로 출력되도록 했다.

// 요일 배열 만들기
const days = ["일", "월", "화", "수", "목", "금", "토"];
const date = new Date().getDay(); // 오늘 요일
let today = date;
const dateFromToday = days.map((v, i) => {
  // 오늘 요일 기준으로 배열 새로 생성
  let result = today;
  if (today >= days.length) {
    // 일요일까지 갔을 경우 월요일로 다시 돌아가도록 설정
    today = 0;
    result = 0;
  }
  today += 1;
  return result;
});
// 부모 컴포넌트에서 만든 dateFromToday > date라는 이름으로 받아옴
{props.date.map((v, i) => {
  return (
    <li key={i} className="day_item">
      <span className="day">
        {props.days[v]} // 새로 생성된 배열을 key값으로 요일 출력
        <em style={{ fontSize: "0" }}>{random_stars[i]}</em>
      </span>
      <ScoreArea>
      /// 생략
      </ScoreArea>
    </li>
  );
})}

완벽한 코드는 아니지만, 어떻게든 일단 구현이 된거에 만족.. ㅜㅜ
알고리즘 주차에 이걸 어떻게 풀지..? 라는 고민에 휩싸여 머리 쥐어뜯고 공부했던 것들이 도움이 된 것 같아서 뿌듯했다.


클릭해서 평점 남기기 기능 구현

상세 페이지에서는, 기본적으로 하나도 체크되지 않은 디폴트 상태의 별 5개가 있고, 그 별을 클릭하여 평점을 남기는 기능을 구현해야 했다.
이 문제는 useState() 훅을 사용했더니 생각보다는 쉽게 해결됐다.

const [score, setScore] = React.useState(-1); // state 생성

<ScoreArea>
  {Array.from({ length: 5 }, (v, i) => {
    return (
      <button
        key={i}
        onClick={() => {
          setScore(i);
        }}
        >
        <img src={i <= score ? StarActive : StarDefault} alt="star"/>
      </button>
    );
  })}
</ScoreArea>

우선 state에 score라는 변수를 생성한 뒤, 클릭해야하는 별, 즉 버튼의 갯수만큼 Array.from()을 활용해 반복문을 돌려 버튼을 그려줬다.
버튼에는 onClick 이벤트를 추가하여 score의 값을 바꿔주는 setScore 함수를 연결했다. 클릭된 버튼의 index값이 예를 들어 2라면, 2 이하의 index를 가진 모든 버튼들이 선택된 것으로 처리하면 될 것 같다고 생각해서 클릭된 버튼에 해당하는 index의 값을 score값에 대입해줬다.

그리고나서 선택된 버튼들의 이미지가 변경되도록 삼항연산자로 조건을 추가했는데, 현재 돌고있는 반복문의 index가 socre값보다 작거나 같을 경우 active된 색상의 이미지가 나오도록 했고, score보다 클 경우는 선택되지 않은 default 색상의 이미지가 나오도록 처리했다.

이 때는 선택된 별 이미지가 바뀌기만 하면 되니까 보여지는 것에만 초점을 둬서 index값을 그대로 score에 반환했는데, 나중에 정말 데이터를 넘겨줘야 하는 상황이 온다면 평점은 1부터 시작하지만 index는 0부터 시작하니까 score의 값은 index에 +1을 더한 값을 돌려주고 이미지 출력하는 조건을 -1로 변경해야 하지 않을까 하는 생각이 들었다.


키보드 숫자키로 평점 남기기 구현

이것도 추가 미션이었지만, 키보드 이벤트만 연결해주면 될 것 같아서 작업해보기로 했다. 상세 페이지에 useEffect를 추가하여 window에 keydown 이벤트 리스너를 추가하였다. 그리고 keydownEvent 함수를 만들어서 e.key의 값이 1,2,3,4,5일 경우에만 생성되어있던 state인 score의 값에 e.key-1을 한 숫자를 대입해줬고(score가 활용되는 html 영역에서는 버튼의 index를 0부터 계산하기 때문에), k.key값이 1~5의 값이 아닐 경우 alert 메세지를 반환하도록 처리했다.

const [score, setScore] = React.useState(-1);

const keyupEvent = (e) => { // keyup 이벤트 추가 (숫자로 평점 입력)
  if (0 < e.key && e.key <= 5) {
    setScore(e.key - 1);
  }else{
    alert('평점 입력은 1에서 5사이의 숫자키만 이용해서 입력 가능합니다.');
  }
};

React.useEffect(() => { // keyup 이벤트 연결
  document.addEventListener("keyup", keyupEvent);
  return () => {
    document.removeEventListener("keyup", keyupEvent);
  };
});

0개의 댓글