본문 바로가기
💻CODING/react. vue

[js] react로 로또 게임 만들기

by 코딩하는 갓디노 2021. 4. 1.

로또

 

리액트 class/hooks로 구현한
로또 게임 입니다.

 

예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다.

 

순수 자바스크립트로 만든 로또 게임은 아래 포스트로 이동해주세요.

https://goddino.tistory.com/103

 

[js] 자바스크립트로 로또추첨기 구현하기

자바스크립트를 이용하여, 로또 추첨기를 구현하는 예제입니다. 예제는 인프런의 제로초, "조현영"님의 강의를 들으면서 공부한 내용입니다. 구현 순서 번호 순서 사용 메서드 1 45개의 후보

goddino.tistory.com

 

client.jsx

import React from 'react';
import ReactDom from 'react-dom';
import Lotto from './Lotto';

ReactDom.render(<Lotto />, document.querySelector('#root'));

 

index.html

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

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>로또 추첨기</title>
    <style>
        .ball { display: inline-block; width: 30px; height: 30px; margin: 10px; 
        line-height: 30px; text-align: center; border-radius: 50%; }
    </style>
</head>

<body>
    <div id="root"></div>
    <script src="./dist/app.js"></script>
</body>

</html>

 

Lotto.jsx - class 방식

import React, { Component } from 'react';
import Ball from './Ball'

function getLottoNums() {
    console.log('getLottoNums');
    //숫자 45개 뽑기
    const candidates = Array(45).fill().map((v, i) => v = i+1 );
    const chosenNum = []; 

    while (candidates.length > 0) {  //45개 후보숫자 랜덤 섞기
        const mixedNum =  candidates.splice(Math.floor(Math.random() * candidates.length), 1)[0];
        chosenNum.push(mixedNum);  
    }
    console.log(chosenNum);
    
    const lottoNum = chosenNum.slice(0, 6).sort((p, c) => p - c  ); //배열
    const bonus = chosenNum[chosenNum.length -1];

    console.log(lottoNum, bonus);
    return [...lottoNum, bonus] 
}

class Lotto extends Component {    
    state = {
        lottoNum: getLottoNums(), //당첨숫자들
        winBalls: [], //lottoNum의 숫자를 winBalls에 넣어줌
        bonus: null, //보너스공, lottoNum의 마지막 숫자를 보너스로 넣어줌
        redo: false, //한번더 버튼 true시 출현
    }

    timeouts = [];

    runTimeouts = () => {
        const {lottoNum} = this.state;
        for(let i=0; i<lottoNum.length -1; i++) { //0-5숫자
            this.timeouts[i] = setTimeout(() => {
                //lottoNum를 winBalls에 push 대체법
                this.setState((prevState) => {
                    return {
                        winBalls: [...prevState.winBalls, lottoNum[i]],
                    };
                });
            }, i * 1000);
        }
        this.timeouts[6] = setTimeout(() => { //마지막 숫자
            this.setState({
                bonus: lottoNum[6],
                redo: true, //true시 한번더 버튼 출현
            });
        }, 7000);
    };

    componentDidMount() { //시작하자마자 실행
        this.runTimeouts();
    }

    //before state가 prevState, after state가 this.state    
    componentDidUpdate(prevProps, prevState) { //업데이트 상황을 조건문으로 지정 중요
        if(this.state.winBalls.length === 0) { //winBalls: [] <- 아래 초기화 문구 
            this.runTimeouts();
        }
    }

    componentWillUnmount() {
        this.timeouts.forEach((item) => {
            clearTimeout(item);
        });
    } 
    
    onClickRedo = () => {
        this.setState({ //초기화
            lottoNum: getLottoNums(), 
            winBalls: [], 
            bonus: null, 
            redo: false,
        });
        this.timeouts = [];
    }

    render() {
        const {winBalls, bonus, redo} = this.state;
        return (
            <div>
                <div>당첨 숫자</div>
                <div id="결과창">
                    {/* 반복되는 부분은 컴포넌트화 */}
                    {winBalls.map((num) => <Ball key={num} number={num} /> )}
                </div>
                <div>보너스</div>
                {/* 조건부 실행문 */}
                {bonus && <Ball number={bonus} />} 
                {redo && <button onClick={redo? this.onClickRedo : () => {}}>한번 더!</button>}
            </div>
        );
    }
}

export default Lotto;

 

Lotto.jsx - hooks 방식

import React, { useState, useRef, useEffect, useMemo } from 'react';
import Ball from './Ball'

function getLottoNums() {
    console.log('getLottoNums');
    //숫자 45개 뽑기
    const candidates = Array(45).fill().map((v, i) => v = i+1 );
    const chosenNum = []; 

    while (candidates.length > 0) {  //45개 후보숫자 랜덤 섞기
        const mixedNum =  candidates.splice(Math.floor(Math.random() * candidates.length), 1)[0];
        chosenNum.push(mixedNum);  
    }
    console.log(chosenNum);
    
    const lottoNum = chosenNum.slice(0, 6).sort((p, c) => p - c  ); //배열
    const bonus = chosenNum[chosenNum.length -1];

    console.log(lottoNum, bonus);
    return [...lottoNum, bonus] 
}

//비동기 요청 처리 
//componentDidMount만 실행할때
//useEffect(() => {
    //ajax 
//}, []);

//const mounted = useRef(false);
//useEffect(() => {
//    if(!mounted.current) { //실행은 되지만 아무것도 안함
//       mounted.current = true; 
//    }else{
//        //ajax
//    }
//},[바뀌는 값]); //componentDidUpdate만 componentDidMount 안함

const Lotto = () => {
    //getLottoNums()가 계속 재실행 되는 것을 막기 위하여 useMemo() 사용
    const getLottoNumsMemo = useMemo(() => getLottoNums(), []); //빈배열이면 getLottoNums()이 재실행되지 않음
    const [lottoNum, setLottoNum] = useState(getLottoNumsMemo); //getLottoNums()에서getLottoNumsMemo로 바꿈
    const [winBalls, setWinBalls] = useState([]);
    const [bonus, setBonus] = useState(null);
    const [redo, setRedo] = useState(false);
    const timeouts = useRef([]);

    useEffect(() => {   
        for(let i=0; i<lottoNum.length -1; i++) { //0-5숫자
            timeouts.current[i] = setTimeout(() => {
                //lottoNum를 winBalls에 push 대체법
                setWinBalls((prev) =>[...prev, lottoNum[i]]);
            }, i * 1000);
        }
        timeouts.current[6] = setTimeout(() => { //마지막 숫자
            setBonus(lottoNum[6],),
            setRedo(true);
        }, 7000);
        return () => {
            timeouts.current.forEach((v) => clearTimeout(v));
        }
    }, [timeouts.current]); //아래 55번줄 바뀌는 시점 넣어줌
    //[]이 빈배열이면 componentDidMount와 동일
    //배열에 요소가 있으면 componentDidMount와 componentDidUpdate 둘다 수행

    const onClickRedo = () => {
        console.log('onClickRedo');
        setLottoNum(getLottoNums());
        setWinBalls([]);
        setBonus(null);
        setRedo(false);
        timeouts.current = [];
    }

    return (
        <div>
            <div>당첨 숫자</div>
            <div id="결과창">
                {/* 반복되는 부분은 컴포넌트화 */}
                {winBalls.map((num) => <Ball key={num} number={num} /> )}
            </div>
            <div>보너스</div>
            {/* 조건부 실행문 */}
            {bonus && <Ball number={bonus} />} 
            {redo && <button onClick={redo? onClickRedo : () => {}}>한번 더!</button>}
        </div>
    );

}

export default Lotto;

 

Ball.jsx

import React, { PureComponent } from 'react';

class Ball extends PureComponent {
    render() {
        let background;
        const {number} = this.props;
        if (number < 10) { 
            background = 'yellow';
        } else if (number < 20) { 
            background = 'aqua'; 
        } else if (number < 30) { 
            background = 'pink'; 
        } else if (number < 40) { 
            background = 'skyblue'; 
        } else { background = 'gold'; }



        return (
            <>
                <div className='ball' style={{background}}>{number}</div>
            </>
        )
    }
}

export default Ball;
반응형

댓글