본문 바로가기
CodeIt_Sprint/React_초급

(10)React_초급_ useState() 실습문제

by 코잼민 2024. 11. 23.

◎ 연습문제1 :

이번엔 배점 기능을 한번 구현해 보겠습니다.

원하는 숫자로 배점을 정하면, 게임이 진행될 때마다 이긴 쪽이 해당하는 점수를 얻는 기능이예요.

input 태그로 배점을 정하고 이것에 따라 점수에 반영하려고 합니다.

여기서 리액트에서 특별한 prop을 하나 사용해 보도록 하겠습니다.

onChange

바로 input 태그의 onChange라는 prop입니다.

이전에 자바스크립트로 input을 다루는 방법을 공부하셨다면, 입력값이 변경될 때마다 이벤트를 받으려면 oninput 이벤트 핸들러 함수로 처리한다는 걸 배웠을 겁니다.

하지만 리액트에서는 onChange 라는 prop을 사용해서 처리합니다. 동작은 oninput 이벤트 핸들러이지만 onChange으로 사용하는 건데요.

의미상으로도 좀 더 직관적이죠? 이와 관련된 내용은 아래 링크를 참고하시면 자세히 살펴보실 수 있을 겁니다.

리액트 공식 문서 - DOM 엘리먼트

이번 실습에서는 아래 세 가지 내용을 다뤄 볼게요.

  1. ○ 이번 실습에서는 App.js 파일에서 handleBetChange 핸들러 함수를 구현해 보겠습니다.
  2.   이벤트에서 얻어 온 값을 bet 스테이트의 값으로 변경할게요.
  3.   그리고 이 함수를 배점 input의 onChange 핸들러로 추가해 볼게요.

(옵션) 생각해 볼 문제

number 타입의 input을 사용하더라도 반드시 1과 9 사이의 정수만 전달되는 게 아닙니다. 키보드로 입력하면 알파벳 같은 문자나 소수점을 입력할 수 있는데요, 이벤트 핸들러 함수에서 값을 관찰하면서 bet state의 값이 반드시 1과 9 사이의 정수가 되도록 만들어 보세요.

실습 결과

※ 풀이과정 :

● 순서1_ App.js 구조 파악 :

  //function App(){
  
  const [hand, setHand] = useState(INITIAL_VALUE); //나의 패 useState(초기값) 초기화
  const [otherHand, setOtherHand] = useState(INITIAL_VALUE); //상대방 패 useState(초기값) 초기화
  const [gameHistory, setGameHistory] = useState([]);//승부 기록 배열로  useState([]) 초기화
  const [score, setScore] = useState(0); //scroe 점수  useState(초기값) 초기화
  const [otherScore, setOtherScore] = useState(0);// 상대방 scroe  useState(초기값)
  const [bet, setBet] = useState(1); //bet? input number 태그에서 승점을 몇점단위로 할지 정하는  useState(초기값)

  const handleButtonClick = (nextHand) => {
    const nextOtherHand = generateRandomHand();//랜덤 가위바위보
    const nextHistoryItem = getResult(nextHand, nextOtherHand);//승부 기록 
    const comparison = compareHand(nextHand, nextOtherHand);//utils.js에서 가져온 가위바위보 판결 메소드
    
    //승부 후 , 업데이트
    setHand(nextHand);//나의 패 : 클릭 버튼의 매개인자 대입
    setOtherHand(nextOtherHand);//상대 패
    setGameHistory([...gameHistory, nextHistoryItem]);//기록
    if (comparison > 0) setScore(score + bet); //내가 이겼을 경우 => 나의 점수 + 배점 
    if (comparison < 0) setOtherScore(otherScore + bet); // 상대방이 이겼을 경우 => 상대방 점수 + 배점
  };
  
  //....생략
  }

 

● 순서2_ input 값을 전달받아 배점 적용 :

○ 1_ input에 값 전달 받기(문자열) => event.target.value

○ 2_ 문자열 => 숫자 형변환 => Number()메소드 //결과 double 소수값

○ 3_  정수로 전달 받기 => Math.floor()//내림 함수

4_ 전달받은 입력값을 Bet 변수에 적용하기 => setBet(1번에서 전달받은 )

○ 5_ 1~4에서 구현한 함수를 input.number 태그에 적용하기 => onChange = {1~4로 작성한 함수}

  //function App(){
  
  //imput에 적용할 메소드 1~4과정
  const handleBetChange = (e) => {
    // 여기에 코드를 작성하세요
    let num = Number(e.target.value);//1,2과정
    
    if(num > 9) num %=10;
    else if(num <1) num =1;

    num = Math.floor(num);

    setBet(num);

  };
  
  //....생략
 		<div>
        	<input type="number" value={bet} min={1} max={9} onChange = {handleBetChange}></input>
      	</div>
  }

 

◎ 연습문제2 :

다음 중 state에 대한 설명으로 올바르지 않은 것을 선택해 주세요.

1. 리액트는 state 값이 바뀔 때 화면을 새롭게 그린다.
2. state의 setter 함수의 이름은 일반적으로 state 이름 앞에 set을 붙여서 짓는다.
3. state는 setter 함수로만 변경해야 한다.

4. let 키워드로 state를 선언하면 직접 state 값을 변경할 수 있다.

※ 해설 :

state 만들 때 활용하는 useState 함수는 호출했을 때 두 개의 요소를 가진 배열을 리턴하는데요. 첫 번째 요소가 state이고 두 번째 요소가 state를 변경할 때 사용하는 setter 함수입니다.

그래서 보통 Destructuring 문법으로 작성하는데, 원하는 state의 이름을 첫 번째 변수로 작성하고, setter 함수의 이름은 state 이름 앞에 set을 붙여 카멜 케이스로 이름을 지어주는 것이 일반적입니다. 

useState 함수로 만들어진 state는 변수에 새로운 값을 할당하는 방식으로 변경하는 것이 아니라 반드시 setter 함수를 통해서 변경해야 합니다.

◎ 연습문제2 :

아래 코드의 addMember 함수는 state가 제대로 변경되지 않는 오류가 있습니다.

다음 중 addMember 함수의 문제를 적절히 해결한 보기를 선택해 주세요.

// ... members와 setMembers는 정상적으로 선언되어 있습니다. members는 배열입니다.

  const addMember = (name) => {
    members.unshift(name);
    setMembers(members);
  };

// ...

const addMember = (name) => {
  setMembers([name, ...members]);
};

const addMember = (name) => {
  members.push(newMember);
  setMembers(members);
};

const addMember = (name) => {
  const newMember = name;
  members.unshift(newMember);
  setMembers(members);
};

const addMember = (name) => {
  const newMembers = members;
  newMembers.unshift(name);
  setMembers(newMembers);
};

const addMember = (name) => {
  members.unshift(name);
  const newMembers = members;
  setMembers(newMembers);
};

 

※ 해설 :

○ 생각1 : addMembet(name) 메소드는 name에 인자를 받고

○ 생각2 : 받은 name을 unshift 프로퍼티 메소드로 member배열에 맨 앞인덱스에 추가하는 메소드이다 (반환형 X)

○ 생각3 : 추가된 member 메소드를 setMember()에 반영하면, 첫번째 인덱스의 주소는 바뀌지 않기 때문에 적용X

○ 생각4 : 그래서, 새로운 배열을 하나 새로 만들어서 name + 기존 member 원소들 ... 순으로 나열된 배열로 다시 업데이트 하고 적용해야함.

○ 생각5 : 그걸 간단히 해주는 것이 spread 문법인거 같음

답 : ①

const addMember = (name) => {
  setMembers([name, ...members]);
};

해설지 :

리액트에서 참조형 state를 사용할 때는 반드시 새로운 값을 만들어서 변경해 주어야 합니다.

주어진 문제에서 addMember 함수가 제대로 동작하지 않는 이유는 members state가 배열, 다시 말해 참조형 state이기 때문인데요.

members state는 배열 값 자체를 가지고 있는 게 아니라 그 배열의 주솟값을 참조하고 있는 겁니다.

때문에 unshift 메소드로 배열 안에 요소를 변경했다고 하더라도 결과적으로 참조하는 배열의 주솟값은 변경된 것이 아니게 되는 것이죠.

결과적으로 리액트 입장에서는 members state가 참조하는 주솟값은 여전히 똑같기 때문에 상태(state)가 바뀌었다고 생각할 수가 없게 됩니다.

그래서 참조형 state를 활용할 때는 반드시 새로운 참조형 값을 만들어 state를 변경해야 하는데요.

가장 간단한 방법은 Spread 문법(...) 을 활용하는 겁니다!

Spread 문법을 활용해서 addMember 함수가 제대로 동작하도록 수정한 코드는 보기 1번입니다