과거 프로그래밍 자료들/React

[웹 게임을 만들며 배우는 React] - 가위바위보, 라이프사이클

평부 2022. 9. 28. 22:49

 

 

출처: https://www.inflearn.com/course/web-game-react/dashboard

 

[무료] 웹 게임을 만들며 배우는 React - 인프런 | 강의

웹게임을 통해 리액트를 배워봅니다. Class, Hooks를 모두 익히며, Context API와 React Router, 웹팩, 바벨까지 추가로 배웁니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

* 라이프 사이클(컴포넌트의 일생)

- 컴포넌트(SPClass.jsx)가 클라이언트(client.jsx)에서 불려와서 렌더링됨

- 컴포넌트가 DOM에 붙는 순간이 있음
- 특정한 동작 가능

class에서는 아래의 3개가 담당한다면 함수 컴포넌트에서는 useEffect가 이 역할을 담당(1대1 대응은 아님)

 

componentDidMount componentWillUnmount componentWillUnmount
렌더링이 처음, 성공적으로 실행 시 실행

리렌더링이 일어날 때는 실행되지 않음
컴포넌트가 제거되기 직전 리렌더링 후에 실행

 

 

* 클래스의 경우

- componentDidMount나 componentDidUpdate에서 모든 state를 조건문으로 분기 처리함

constructor => render => ref => componentDidMount => setState나 props 바뀔 때 => shoudComponentUpdate

=> render => componentDidUpdate => 부모가 나(자식컴포넌트)를 없앨 때 => componentWillUnmount

 

 

* 비동기 요청

- setInterval 일정 시간마다 반복작업
=렌더링 후 계속 반복작업(누가 취소하지 않음 => 웹사이트 끌 때까지 계속 실행되는 중)
- RSP가 계속 DOM에 붙었다 땟다 하면 기존의 setInterval이 점점 더 많이 생김
- 메모리를 계속 먹음(메모리 누수)
- 완료되지 않은 비동기는 componentWillUnmount에서 정리해야 함

 

 

* 클로저 문제

-  비동기 안에서 바깥에 있는 변수 참조 시 발생

 //클로저
 //const { imgCoord } = this.state; //여기에 위치하면 클로저 문제 발생
 changeHand = () => {
    const { imgCoord } = this.state; //이곳에 위치해야 함
    if (imgCoord === rspCoords.바위) {
      this.setState({
        imgCoord: rspCoords.가위,
      });
    } else if (imgCoord === rspCoords.가위) {
      this.setState({
        imgCoord: rspCoords.보,
      });
    } else if (imgCoord === rspCoords.보) {
      this.setState({
        imgCoord: rspCoords.바위,
      });
    }
  };

 

 

* 고차함수 (아래의 예시는 함수의 형태로 리턴하기 위해 고차함수를 사용한 것)

-  함수 안에 함수 호출하는 부분이 들어 있음

▶ 함수를 인자로 받을 수 있고, 함수의 형태로 리턴할 수 있음

▶ 추상화 작업(복잡한 어떤 것을 압축해 핵심만 추출)을 통해 함수를 전달받거나 함수를 리턴함 

출처 : https://hanamon.kr/javascript-%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98%EC%99%80-%EC%BD%9C%EB%B0%B1-%EC%9D%BC%EA%B8%89%EA%B0%9D%EC%B2%B4%EB%9E%80/

 

[JavaScript] 고차 함수와 콜백(Callback) - 일급 객체란? - 하나몬

❗️함수는 일급 객체이다. 자바스크립트에는 특별한 대우를 받는 일급 객체가 있다. 대표적인 일급 객체 중 하나가 바로 함수이다. 👉 다음 조건을 만족하는 객체를 일급 객체라고 한다. 무명

hanamon.kr

//고차함수
onClickBtn = (choice) => () => {
    const { imgCoord } = this.state;
    clearInterval(this.interval);
    const myScore = scores[choice];
    const comScore = scores[computerCoice(imgCoord)];
    const diff = myScore - comScore;
    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, 1000);
    }, 1000);
  };
 
 <button id="paper" className="btn" onClick={this.onClickBtn("보")}>
 //고차함수가 아니라 onClickBtn = (choice) => {} 일 경우
 // <button id="paper" className="btn" onClick={() => this.onClickBtn("보")}>

 

* class 사용 [RSPClass.jsx]

import React, { Component } from "react";

const rspCoords = {
  바위: "0",
  가위: "-142px",
  보: "-284px",
};

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

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

class RSPClass extends Component {
  state = {
    result: "",
    imgCoord: rspCoords.바위,
    score: 0,
  };

  //interval 문제 해결
  interval;

  componentDidMount() {
    this.interval = setInterval(this.changeHand, 100);
  } //비동기요청

  componentWillUnmount() {
    //비동기요청 정리
    clearInterval(this.interval);
  }

  //   componentDidUpdate() {}

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

  onClickBtn = (choice) => () => {
    const { imgCoord } = this.state;
    clearInterval(this.interval);
    const myScore = scores[choice];
    const comScore = scores[computerCoice(imgCoord)];
    const diff = myScore - comScore;
    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, 1000);
    }, 1000);
  };

  render() {
    const { result, score, imgCoord } = this.state;
    return (
      <>
        <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>
        </div>
        <div>{result}</div>
        <div>현재 {score}점</div>
      </>
    );
  }
}

export default RSPClass;

 

 

* Hooks 사용 

- useEffect(class의 componentDidMount나 componentDidUpdate 역할 담당)

첫번째 파라미터에는 함수, 두번째 파라미터에는 의존값이 들어있는 배열 (deps)을 넣음

▶ 배열에는 꼭 useEffect를 다시 실행할 값만 넣을 것
▶ 두번째 배열에 넣은 이미지가 실행될 때마다(반복) setInterval같은 효과
▶ 두 번째 배열을 비워놓는 건(componentDIdMount) 처음에만 실행되고 다음 것은 바뀌지 않음 
▶ 두 번째 배열에 값을 넣으면 componentDidUpdate 효과

 

참고 : https://react.vlpt.us/basic/16-useEffect.html

 

16. useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기 · GitBook

16. useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기 이번에는 useEffect 라는 Hook 을 사용하여 컴포넌트가 마운트 됐을 때 (처음 나타났을 때), 언마운트 됐을 때 (사라질 때), 그리

react.vlpt.us

import React, { useState, useRef, useEffect } from "react";

const rspCoords = {
  바위: "0",
  가위: "-142px",
  보: "-284px",
};

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

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

const RSP = () => {
  const [result, setResult] = useState("");
  const [imgCoord, setImgCoord] = useState(rspCoords.바위);
  const [score, setScore] = useState(0);
  const interval = useRef();

  useEffect(() => {
    //함수
    //componentDidMount, componentDidUpdate 역할(1대1 대응은 아님)
    console.log("다시 실행");
    interval.current = setInterval(changeHand, 100);
    return () => {
      console.log("종료");
      //componentWillUnmount 역할
      clearInterval(interval.current);
    };
  }, [imgCoord]); //배열(두 번째 인수 배열에 넣은 값들이 바뀔 때 useEffect 실행됨)

  const changeHand = () => {
    if (imgCoord === rspCoords.바위) {
      setImgCoord(rspCoords.가위);
    } else if (imgCoord === rspCoords.가위) {
      setImgCoord(rspCoords.보);
    } else if (imgCoord === rspCoords.보) {
      setImgCoord(rspCoords.바위);
    }
  };
  const onClickBtn = (choice) => () => {
    clearInterval(interval.current);
    const myScore = scores[choice];
    const comScore = scores[computerCoice(imgCoord)];
    const diff = myScore - comScore;
    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, 100);
    }, 1000);
  };
  return (
    <>
      <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={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 RSP;