본문 바로가기
CodeIt_Sprint/React_초급

(17)React_초급_DiceGame_React_Project_1_JSX작성부분

by 코잼민 2024. 11. 26.

 

https://kojammin.tistory.com/199

 

(15)React_초급_React_Design_최종 실습문제 2문제(이건 여러번 복습)

◎ 연습문제1 : 앞의 레슨들에서는 리액트의 개념을 배우면서 기능을 하나씩 완성해 보았습니다. 이제 남은 것은 이미지를 배치하고 CSS로 디자인을 마무리하는 거겠죠? 완성에 필요한 이미지 파

kojammin.tistory.com

위의 문제의 Dicegame 프로젝트를 만들어본 과정입니다.

◎ 1_ index.js 작성 과정 //직접적으로 출력되는 js파일

●  [index.html]코드를 봤을 때, <body>태그 내 자식인 div#root 안에서 제작될 것이다.

-index.html

<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="utf-8" />
  <title>주사위 게임</title>
</head>

<body>
  <div id="root"></div>
</body>

</html>

●  그래서, index.js는 아래와 같이 작성 시작

  • import를 통해 ReactDom을 불러온다. //React 웹페이지 출력에 총 담당자 객체
  • ReactDom 객체render()메소드로 렌더링 시작 

※참고_ReactDom.render()

* React 프로젝트의 출력을 담당하는 객체 프로퍼티 메소드로써,

매개인자1_ 출력할 컴포넌트 태그

매개인자2_ html문서에서 어느 태그의 자식에서 제작할지 결정할지 경정해주는 요소노드 값

-index.js

import React from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();

 

◎ 2_ App.js //전체적으로 DiceGame 웹페이지의 전체 컴포넌트 구조를 나타내주는 컴포넌트

  • index.js의 render()에 <App>태그 하나만 출력되도록 한다. => ReactDom.render의 매개인자1에 <App>컴포넌트 태그
  • 주사위 게임 출력 장면으로, <App> 컴포넌트 내의 구조를 작성해보자
  • 그리고, App.js는 각 컴포넌트에서 나오는 데이터를 모두 전달 받고, 출력만 한다.

- App.js //틀만 작성한 코드

import HeadImg from './assets/logo.png';

function App() {
  return (
    <>
      <div className='Header'>
        <img src={HeadImg} alt='head-logo-img'></img>
        <h1>주사위 게임</h1>
      </div>
      <div className='Button'>

      </div>
      <div className='Board'>

      </div>
    </>
  );
}

export default App;

 

 

◎ 3_ Dice.js 작성 과정

● 순서1_ [src]폴더의 주사위 사진들 모두 import 하기

//주사위 사진 담당 부분
//Blue 사진
import DiceBlue01 from './assets/dice-blue-1.svg';
import DiceBlue02 from './assets/dice-blue-2.svg';
import DiceBlue03 from './assets/dice-blue-3.svg';
import DiceBlue04 from './assets/dice-blue-4.svg';
import DiceBlue05 from './assets/dice-blue-5.svg';
import DiceBlue06 from './assets/dice-blue-6.svg';

//Red 사진
import DiceRed01 from './assets/dice-red-1.svg';
import DiceRed02 from './assets/dice-red-2.svg';
import DiceRed03 from './assets/dice-red-3.svg';
import DiceRed04 from './assets/dice-red-4.svg';
import DiceRed05 from './assets/dice-red-5.svg';
import DiceRed06 from './assets/dice-red-6.svg';

● 순서2_ Blue / Red 냐에 따라 6개씩 나눠지고, 숫자 눈에 따라 다르므로, => 배열이 아닌, 객체로 미리 변수들을 저장한다.

const ImgSrcs = {
  blue : [DiceBlue01, DiceBlue02, DiceBlue03, DiceBlue04, DiceBlue05, DiceBlue06],
  //Blue 주사위 Img Src 보관
  red : [DiceRed01, DiceRed02, DiceRed03, DiceRed04, DiceRed05, DiceRed06],
  //Red 주사위 Img Src 보관
}

 

● 순서3_ <Dice>컴포넌트의 function 작성

○ props.color , props.num을 매개로 받아, 그 값에 따라 적절한 주사위 눈의 사진변수를 고를 수 있도록 한다.

function Dice({color, num}) {

  const Src = ImgSrcs[color][num-1];
  const Alt = `Dice-${color}_${num}`;
  return <img src={Src} alt={Alt}/>;
}


export default Dice; //외부에서 Dice 컴포넌트를 사용할 수 있도록 함

 

//일단 버튼부터 만들어보자

◎ 4_ Button.js 작성 과정

● 순서1_"던지기", "처음부터"의 textContent의 버튼 만듪기 (기능은 아X) => props.Children 객체 프로퍼티 이용 //여기서 문제가 많이 발생함

//Button.js

function Button({children})//children인, props 객체 프로퍼티를 이용하여,
{
  return (<button>{children}</button>); //컴포넌트 태그 TextContent에 바로 작성될 수 있도록 한다.
}

export default Button;

※ 문제_ React 최신버전만 children 객체가 가능했던것 같음

=> 해결방법 :

import React from 'react';//얘도 import 추가
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));//가장 상위 노드를 변수로 저장하고
root.render(<App/>);//그 상위 노드의 객체 프로퍼티 메소드인, render메소드 내 포함

 

● 순서2_ App.js에 <Button>컴포넌트 태그 2개 추가 //"던지기", "처음부터"

//App.js
import HeadImg from './assets/logo.png';
import Button from './Button';

function App() {
  return (
    <>
      <div className='Header'>
        <img src={HeadImg} alt='head-logo-img'></img>
        <h1>주사위 게임</h1>
      </div>
      <div>
      //childern객체 프로퍼티를 이용하여, 바로 태그 내에 텍스트를 출력할 수 있도록 한다.
        <Button>던지기</Button>
        <Button>처음부터</Button>
      </div>
      <div className='Board'>

      </div>
    </>
  );
}

export default App;

 

● 순서3_ useState 메소드 사용

○ 기본적인 useState() 사용  공식 :

import { useState } from 'react'; //import로 useState 사용할 수 있도록 라이브러리 불러오기

const [조작할 변수, 조작할 수 있도록 하는 메소드] = useState(변수의 초기값);

//클릯했을 때, 핸들러에 useState 적용
//ex_ 버튼을 누를 시, 값이 1이 되도록 한다.
const handleClick = () => {
	조작할 수 있도록 하는 메소드(2);
};

//버튼 태그에 적용
<button onClick = {handleClick}></button>

○ App.js에 "던지기" <Button> 컴포넌트와 Dice를 추가하여, "던지기" 버튼을 누를 시, 주사위 눈이 랜덤하게 바뀌는 작동 구현하기

//App.js
//1_ 필요한 import문 작성
import { useState } from 'react';//useState 사용을 위한 import문
import HeadImg from './assets/logo.png';
import Button from './Button';//<Button> 컴포넌트 태그 사용을 위한 import문
import Dice from './Dice';//<Dice> 컴포넌트 태그 사용을 위한 import문

//2_ 1~6 넌수 발생 메소드
function randNum()
{
  return Math.floor(Math.random()*(6-0)) +1;
}

function App() {

  //3. 주사위 눈에 대한 useState()
  const [myNum, setMyNum] = useState(1);

  //4. "던지기"버튼에 대한 핸들러 메소드
  const handleRollClick = ()=>
  {
    setMyNum(randNum());
    //버튼 누를 시myNum 변수값을 난수로 설정
  };

  return (
    <>
      <div className='Header'>
        <img src={HeadImg} alt='head-logo-img'></img>
        <h1>주사위 게임</h1>
      </div>
      <div>
      	//5. "던지기"버튼에 onClick 속성값에 핸들러 적용
        <Button onClick={handleRollClick}>던지기</Button>
        <Button>처음부터</Button>
      </div>
      <div className='Board'>
      //6. 위에서 버튼 적용 후, 바뀐 myNum값을 num의 프로퍼티에 적용
          <Dice color='blue' num={myNum}/>
      </div>
    </>
  );
}

export default App;

출력 장면 :

이제 같은방식으로,  "처음부터" 버튼도 추가로 적용

//App.js에서 추가된 부분

function App() {

//"처음부터" 버튼 누를 시 , 작동될 핸들러
 const handleRollReset = () =>
  {
    setMyNum(1);//myNum값 1로 되올리도록 설정
  }
 
 <Button onClick={handleRollReset}>처음부터</Button>
 //"처음부터" 버튼 태그에 onCLick속성값에 핸들러 적용

//..
}

마지막으로 총점, 기록 적용

import { useState } from 'react';
import HeadImg from './assets/logo.png';
import Button from './Button';
import Dice from './Dice';

function randNum()//1~6 넌수 발생 메소드
{
  return Math.floor(Math.random()*(6-0)) +1;
}

function App() {

  //주사위 눈에 대한 useState()
  const [myNum, setMyNum] = useState(1);//나
  const [otherNum, setOtherNum] = useState(1);//상대


  //배열 선언
  const [myRecord,setMyRecord] = useState([]);
  const [otherRecord,setOtherRecord] = useState([]);

  //던지기에 대한 핸들러 메소드
  const handleRollClick = ()=>
  {
    const blueNum = randNum();
    const redNum = randNum();
    
    //주사위 눈
    setMyNum(blueNum);
    setOtherNum(redNum);

        // 기록 업데이트
        setMyRecord((myRecord) => [...myRecord, blueNum]);
        setOtherRecord((otherRecord) => [...otherRecord, redNum]);

  };

  const handleRollReset = () =>
  {
    //주사위 눈 1로 Reset
    setMyNum(1);
    setOtherNum(1);


    //기록을 빈 배열로 초기화
    setMyRecord([]);
    setOtherRecord([]);
  }

    // 총점 계산 (렌더링 시 계산)
    const mySum = myRecord.reduce((a, b) => a + b, 0);
    const otherSum = otherRecord.reduce((a, b) => a + b, 0);

  return (
    <>
      <div className='Header'>
        <img src={HeadImg} alt='head-logo-img'></img>
        <h1>주사위 게임</h1>
      </div>
      <div>
        <Button onClick={handleRollClick}>던지기</Button>
        <Button onClick={handleRollReset}>처음부터</Button>
      </div>
      <div className='Board'>
          <Dice color='blue' num={myNum} record = {myRecord}/>
          <h2>나의 총점</h2>
          <p>{mySum}</p>
          <h2>나의 기록</h2>
          <p>{myRecord.join(", ")}</p>
          <Dice color='red' num={otherNum} record = {otherRecord}/>
          <h2>상대의 총점</h2>
          <p>{otherSum}</p>
          <h2>상대의 기록</h2>
          <p>{otherRecord.join(", ")}</p>
      </div>
    </>
  );
}

export default App;

◆ 여기서 내가 어려웠던 부분 :

- 요인1_ 핸들러에 배열 업데이트 방법 :

//"던지기" 버튼 누를 시, 작동될 핸들러
const handleRollClick = ()=>
{
	//배열 업데이트 부분
    setMyRecord((myRecord)=>[...MyRecord, 새로 추가할 값]);
  
    
}

- 요인2_ 총점의 값은 다 기록된 후의 합을 구하는 것이므로,  핸들러 코드 부분 ~ return 문 사이에 처리하는 것

function App() {


//"던지기" 버튼 누를 시, 작동될 핸들러
const handleRollClick = ()=>
{
 //...생략   
}
//"처음부터" 버튼 누를 시, 작동될 핸들러
  const handleRollReset = () =>
  {
   //...생략   
  }
  
   // 총점 계산 (렌더링 시 계산)
    const mySum = myRecord.reduce((a, b) => a + b, 0);
    const otherSum = otherRecord.reduce((a, b) => a + b, 0);
  
  return (<>-</>)
 }

그리고, 배열의 합을 변수의 저장하는 코드는 암기해두면, 좋다.

const Sum = 배열.reduce((a,b)=>a+b,0);

=> 설명 : 배열이 비어있다면, 초기값 0이고, 왼쪽 인덱스 순으로 

0+a

a + b

b + 다음 인덱스 원소 ,. 로 누적합 계산

최종 코드 (App.js)

import { useState } from 'react';
import HeadImg from './assets/logo.png';
import Button from './Button';
import Dice from './Dice';

function randNum()//1~6 넌수 발생 메소드
{
  return Math.floor(Math.random()*(6-0)) +1;
}

function App() {

  //주사위 눈에 대한 useState()
  const [myNum, setMyNum] = useState(1);//나
  const [otherNum, setOtherNum] = useState(1);//상대


  //배열 선언
  const [myRecord,setMyRecord] = useState([]);
  const [otherRecord,setOtherRecord] = useState([]);

  //던지기에 대한 핸들러 메소드
  const handleRollClick = ()=>
  {
    const blueNum = randNum();
    const redNum = randNum();
    
    //주사위 눈
    setMyNum(blueNum);
    setOtherNum(redNum);

        // 기록 업데이트
        setMyRecord((myRecord) => [...myRecord, blueNum]);
        setOtherRecord((otherRecord) => [...otherRecord, redNum]);

  };

  const handleRollReset = () =>
  {
    //주사위 눈 1로 Reset
    setMyNum(1);
    setOtherNum(1);


    //기록을 빈 배열로 초기화
    setMyRecord([]);
    setOtherRecord([]);
  }

    // 총점 계산 (렌더링 시 계산)
    const mySum = myRecord.reduce((a, b) => a + b, 0);
    const otherSum = otherRecord.reduce((a, b) => a + b, 0);

  return (
    <>
      <div className='Header'>
        <img src={HeadImg} alt='head-logo-img'></img>
        <h1>주사위 게임</h1>
      </div>
      <div>
        <Button onClick={handleRollClick}>던지기</Button>
        <Button onClick={handleRollReset}>처음부터</Button>
      </div>
      <div className='Board'>
          <Dice color='blue' num={myNum} record = {myRecord}/>
          <h2>나의 총점</h2>
          <p>{mySum}</p>
          <h2>나의 기록</h2>
          <p>{myRecord.join(", ")}</p>
          <Dice color='red' num={otherNum} record = {otherRecord}/>
          <h2>상대의 총점</h2>
          <p>{otherSum}</p>
          <h2>상대의 기록</h2>
          <p>{otherRecord.join(", ")}</p>
      </div>
    </>
  );
}

export default App;

◎ 5_ [ Dice + 총점 +기록 ]을 <Board> 컴포넌트로 감싸기

● 순서1_ 전략짜기

○ Board 구조 :

//Board.js
//주사위 총점, 기록 안내 기록을 한 컴포넌트로 감싸기

import Dice from "./Dice";

function Board(){
  
  return (
    <>
      <h2>{name}</h2>
      <Dice color={color} num={num}></Dice>
      <h2>{name}의 총점</h2>
      <p>{sum}</p>
      <h2>{name}의 기록</h2>
      <p>{record.join(", ")}</p>
    </>
  );
}

○ Dice의 num, sum, record 처리 :

  • App.js 에 Record를 useState를 한뒤, Board 컴포넌트에 전달한다.
  • Board,.js에는 App.js에 버튼을 누른 뒤의 Record를 매개인자로 전달받아 , 마지막번째 나온 눈의 수 == num , 배열의 reduce 객체 프로퍼티 메소드로 합(Sum)을 처리한다. 

- App.js

import { useState } from 'react';
import HeadImg from './assets/logo.png';
import Button from './Button';
import Board from './Board';

function randNum()//1~6 넌수 발생 메소드
{
  return Math.floor(Math.random()*(6-0)) +1;
}

function App() {

  //배열 선언
  const [myRecord,setMyRecord] = useState([]);
  const [otherRecord,setOtherRecord] = useState([]);

  //던지기에 대한 핸들러 메소드
  const handleRollClick = ()=>
  {
    const blueNum = randNum();
    const redNum = randNum();

        // 기록 업데이트
        setMyRecord((myRecord) => [...myRecord, blueNum]);
        setOtherRecord((otherRecord) => [...otherRecord, redNum]);

  };

  const handleRollReset = () =>
  {


    //기록을 빈 배열로 초기화
    setMyRecord([]);
    setOtherRecord([]);
  }


  return (
    <>
      <div className='Header'>
        <img src={HeadImg} alt='head-logo-img'></img>
        <h1>주사위 게임</h1>
      </div>
      <div>
        <Button onClick={handleRollClick}>던지기</Button>
        <Button onClick={handleRollReset}>처음부터</Button>
      </div>
      <div className='Board'>
        <Board name="나" color="blue" record={myRecord}></Board>
        <Board name="상대" color="red" record={otherRecord}></Board>
      </div>
    </>
  );
}

export default App;

- Board.js

import Dice from "./Dice";


function Board({ name, color,record}) {
  
  let num = 1;//초반 화면은 배열이 비어있으므로, default값 1로 처리
  if(record.length>0)
  {
    num = record[record.length-1]//배열원소가 있다면, 배열 마지막 원소를 눈의 수로 한다.
  }

  const sum = record.reduce((a,b)=>a+b,0);

  return (
    <>
      <h2>{name}</h2>
      <Dice color={color} num={num}></Dice>
      <h2>{name}의 총점</h2>
      <p>{sum}</p>
      <h2>{name}의 기록</h2>
      <p>{record.join(", ")}</p>
    </>
  );
}

export default Board;

 출력 장면 :