카테고리 없음

react로 가위바위보 구현하기! + Hooks로 변환

공대키메라 2021. 9. 25. 19:12

내가 정리하는 react내용은 전부 zerocho teacher의 강의로부터 가져온 것이다. 

 

무료 강의 감사합니다. ㅠㅠ 우리모두 inflearn의 zerocho react강의로 공부해용!~

 

강의명 : 웹 게임을 만들며 배우는 React - zerocho 5-1 가위바위보

 

react를 접하면서 가장 어려웠던 부분이 세팅하는 부분이었고 

 

그다음이 아마 어떤 방식으로 코드를 짜야 하는지인것 같다.

 

어떤 언어를 접하더라도 처음 접하게 되면 그것은 누구나 다 똑같으리라

 

이번 가위바위보를 구현하면서 처음본 부분이 이제 componentDidMount, componentDidUpdate, componentWillUnmount, useEffect 등이 있다.

 

이러한 부분을 실제로 실무에서 어떻게 쓰는지 코드를 알 길이 없네... ㅠㅠ 

 

목표는 react와 spring jpa를 이용한 미니 프로젝트인데 이게 react도 처음 접해보는 것이고 spring jpa같은 경우에도 내가 강의만 차근차근 들으면서 공부해서 두개의 숙련도 모두 떨어진다. 

 

그래서 필자는 어떻게 공부 방향을 진행해야 하는지 큰 시름에 빠졌다고 한다...

 

RSP.jsx

 

import React, { Component } from 'react';

// 작동 순서 보기
// 클래스의 경우 -> constructor -> render -> ref -> componentDidMount 
// -> (setState / props 바뀔 때 -> shouldComponentUpdate -> render -> componentDidUpdate)
// 부모가 나를 없앨 때 -> componentWillUnmount -> 소멸

const rspCoord ={
    바위: '0',
    가위: '-142px',
    보: '-284px',
}

const scores = {
    가위: 1,
    바위: 0,
    보: -1,
}

const ComputerChoice = (imgCoord) => {
    return Object.entries(rspCoord).find(function(v){
        return v[1] === imgCoord;
    })[0];
};

class RSP extends Component { 
    state = {
        result: '',
        imgCoord: rspCoord.바위,
        score: 0,
    };

    interval;

    //컴포턴크가 첫 렌더링에 성공해서 실행되면 componentDidMount가 실행된다. 
    //setState를 쓰고 싶은에 어디서 써야할지 모를 때 componentDidMount에서 쓸 수 있음
    componentDidMount() {
        this.interval = setInterval(this.changeHand, 200);
    };

    changeHand = () => {
        const {imgCoord} = this.state;
        if(imgCoord === rspCoord.바위){
            this.setState({
                imgCoord: rspCoord.가위,
            });
        } else if (imgCoord === rspCoord.가위){
            this.setState({
                imgCoord: rspCoord.보,
            });
        } else if (imgCoord === rspCoord.보){
            this.setState({
                imgCoord: rspCoord.바위,
            });
        }
    }

    //state나 props가 변경될 때 rerendering이 발생
    //리렌더링 후에 실행된다.
    //짱깬보에서는 사용 안할거임
    // componentDidUpdate() { 

    // }

    //컴포넌트가 제거되기 직전에 실행된다. 
    //componentDidMount에서 한 작업들을 제거하는 용도
    //비동기 요청 정리를 많이 함
    //비동기 처리가 남아있다면 conponentDidMount안에 일들을 여기 안에서 내가 직접 처리해줘야함. 
    componentWillUnmount() {
        clearInterval(this.interval);
    };

    // onClickBtn = (choice) => {
    onClickBtn = (choice) => () => {
        const {imgCoord} = this.state; 
        clearInterval(this.interval);
        const myScore = scores[choice];
        const cpuScore = scores[ComputerChoice(imgCoord)];
        const diff = myScore - cpuScore;
        if(diff === 0){
            this.setState({
                result: '비겼습니다!',
            });
        } else if ([-1, 2].includes(diff)) {
            this.setState((prevState) => {
                return {
                    result: '이겼습니다!',
                    score: prevState.score + 1,
                };
            });
        } else {
            this.setState((prevState) => {
                return {
                    result: '졌습니다!',
                    score: prevState.score - 1,
                };
            });
        }
        setTimeout(() => {
            this.interval = setInterval(this.changeHand, 200);
        }, 2000)
    }

    render() {//setState가 render안에는 들어가면 안된다.
        const {result, score, imgCoord} = this.state;
        return ( 
            <>
                {imgCoord}
                <div id="computer" style={{ background : `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
                <div>
                    {/* <button id="rock" className="btn" onClick={() => this.onClickBtn('바위')}>바위</button>
                    <button id="scissor" className="btn" onClick={() => this.onClickBtn('가위')}>가위</button>
                    <button id="paper" className="btn" onClick={() => this.onClickBtn('보')}>보</button> */}
                    <button id="rock" className="btn" onClick={this.onClickBtn('바위')}>바위</button>
                    <button id="scissor" className="btn" onClick={this.onClickBtn('가위')}>가위</button>
                    <button id="paper" className="btn" onClick={this.onClickBtn('보')}>보</button>
                </div>
                <div>결과 {result}</div>
                <div>현재 {score} 점</div>
            </>
        );
    }
}

export default RSP;

// 순서 예시
// 1. class가 client.jsx에서 선언되는 순간
//      constructor 부분이랑 method 들이 class에 붙어서 처음 rendering함
// 2. 첫 렌더링이 끝나면 componentDidMount가 실행됨
// 3.  shouldComponentUpdate(nextProps, nextState, nextContext){} 의 return 값이 true 라면
//      rerendering이 일어남. 그 후 componentDidUpdate가 실행됨 (false면 rerendering 안함)
// 4. 부모가 나를 없앨 때 -> componentWillUnmount 해서 화면에서 rendering한게 사라짐

 

간단하게 다시 말해보면 

 

componentDidMount는 그냥 영어 component did mount 를 직관적으로 보면

 => 컴포넌트가 mount 했다! => rendering 한 다음에 일 처리 

 

다 끝나면 componentWillUnmount => 컴포넌트 unmount할거야! => 렌더링 한 것 지울때 일 처리!

 

그냥 해석하면 그 module이나 메소드의 기능을 짐작할 수 있도록 코드를 짜는것이 좋다고 한다. 

 

근데 hook에서는 약간 헷갈린다! 

 

 

RSPHooks.jsx

import React, { useState, useRef, useEffect, memo } from 'react';

// 작동 순서 보기
// 클래스의 경우 -> constructor -> render -> ref -> componentDidMount 
// -> (setState / props 바뀔 때 -> shouldComponentUpdate -> render -> componentDidUpdate)
// 부모가 나를 없앨 때 -> componentWillUnmount -> 소멸

const rspCoord ={
    바위: '0',
    가위: '-142px',
    보: '-284px',
}

const scores = {
    가위: 1,
    바위: 0,
    보: -1,
}

const ComputerChoice = (imgCoord) => {
    return Object.entries(rspCoord).find(function(v){
        return v[1] === imgCoord;
    })[0];
};

//                      result, imgCoord, score
// componentDidmount
// componentDidUpdate
// componentWillUnmount
// 

const RSPHooks = memo(() => {

    const [result, setResult] = useState('');
    const [imgCoord, setImgCoord] = useState(rspCoord.바위);
    const [score, setScore] = useState(0);
    const interval = useRef();

    useEffect (() => { // componentDidMount, componentDidUpdate 역할 (1대1 대응은 아님). useEffect를 여러번 사용할 수도 있음
        interval.current = setInterval(changeHand(),100);
        return () => { // componentWillUnmount 역할
            clearInterval(interval.current);
        }
    }, [imgCoord]); //useEffect를 실행하고 싶은 state 를 배열 안에 넣어줘야 한다.  

    const changeHand = () => {
        if(imgCoord === rspCoord.바위){
            setImgCoord(rspCoord.가위);
        } else if (imgCoord === rspCoord.가위){
            setImgCoord(rspCoord.보);
        } else if (imgCoord === rspCoord.보){
            setImgCoord(rspCoord.바위);
        }
    }

    const onClickBtn = (choice) => () => {
        clearInterval(interval);
        const myScore = scores[choice];
        const cpuScore = scores[ComputerChoice(imgCoord)];
        const diff = myScore - cpuScore;
        if(diff === 0){
            setResult('비겻습니다!');
        } else if ([-1, 2].includes(diff)) {
            setResult('이겼습니다!');
            setScore((prevScore) => prevScore + 1);
        } else {
            setResult('이겼습니다!');
            setScore((prevScore) => prevScore + -1);
        }
        setTimeout(() => {
            interval.current = setInterval(changeHand, 200);
        }, 2000)
    }
    
    return ( 
        <>
            {imgCoord}
            <div id="computer" style={{ background : `url(https://en.pimg.jp/023/182/267/1/23182267.jpg) ${imgCoord} 0` }} />
            <div>
                {/* <button id="rock" className="btn" onClick={() => this.onClickBtn('바위')}>바위</button>
                <button id="scissor" className="btn" onClick={() => this.onClickBtn('가위')}>가위</button>
                <button id="paper" className="btn" onClick={() => this.onClickBtn('보')}>보</button> */}
                <button id="rock" className="btn" onClick={onClickBtn('바위')}>바위</button>
                <button id="scissor" className="btn" onClick={onClickBtn('가위')}>가위</button>
                <button id="paper" className="btn" onClick={onClickBtn('보')}>보</button>
            </div>
            <div>결과 {result}</div>
            <div>현재 {score} 점</div>
        </>
    );
});

export default RSPHooks;

 

Hooks 형식에서 

 

componentDidMount, componentWillUnmount, componentDidUpdate를 어떻게 구현하는가?

 

useEffect모듈을 import하면 해결된다고 한다. 

 

다른 점은 useEffect안에서 위 세개를 조작해야 한다는 사실!

 

사용법은 여기서 배운 것 까지만 나도 알고 잘 모르겟다. 

 

후에 더 찾아보도록 하겠다.