[웹 게임을 만들며 배우는 React] 가위바위보 (라이프사이클-componentDidMount, componentDidUpdate, componentWillUnmount, useEffect, 커스텀 hook)

안지수·2023년 4월 14일
0
post-custom-banner
  • render안에 setState 쓰면 무한히 수행

👑 class component 라이프사이클

componentDidMount

: 렌더가 성공적으로 처음 실행된 후 실행
-> 리렌더링 일어날 때는 실행되지 않음
-> 여기에 비동기 요청 많이 함 (setInterval)

componentWillUnmount

: 컴포넌트가 제거되기 직전
-> 여기서 비동기 정리
----> 위 2개가 쌍

componentDidUpdate

: 리렌더링 후 실행

컴포넌트의 라이프사이클

  • 클래스: contructor -> rendering -> Ref -> componentDidMount -> (state/props 바뀔 때) -> shouldComponentUpdate -> 리렌더링 -> componentDidUpdate
  • 부모가 나를 없앴을 경우: componentWillUnmount -> 소멸

👑 함수 component 라이프사이클

즉, class 컴포넌트에서는 componentDidMount나 componentDidUpdate에서 state들을 조건문으로 분기처리/ 함수 컴포넌트에서는 각 state들에 대해 각각 useEffect 만들어 줄 수 있음

  • class 컴포넌트: 가로 (즉, componentDidMount에서 result, imgCoord, score라는 state들을 모두 조작할 수 있고)
  • 함수 컴포넌트: 세로 (즉, useEffect 하나가 result 하나 담당하고,, 이런식으로/ useEffect 하나가 동시에 처리할 수 있긴 함)
  • will 접두사가 붙은 함수는 어떤 작업을 처리하기 직전에 호출되는 함수이고 did 접두사가 붙은 함수는 어떤 작업을 처리한 후

👑 가위바위보 클래스 컴포넌트 구현

import React, { Component } from 'react';

// 클래스의 경우 -> constructor -> render -> ref -> componentDidMount
// (setState/props 바뀔때) -> shouldComponentUpdate(true) -> render -> componentDidUpdate
// 부모가 나를 없앴을 때 -> componentWillUnmount -> 소멸

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

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

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

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

  interval;

  componentDidMount() { // 컴포넌트가 첫 렌더링된 후, 여기에 비동기 요청을 많이 해요
    this.interval = setInterval(this.changeHand, 100);
  }

  componentWillUnmount() { // 컴포넌트가 제거되기 직전, 비동기 요청 정리를 많이 해요
    clearInterval(this.interval);
  }

  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 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, 100);
    }, 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 RSP;

👑 가위바위보 함수 컴포넌트 (hooks로 변환)

-> componenDidMount, componentDidUpdate 대신에 useEffect로!! 1대일1 대응은 아니지만, 그 2개의 역할을 함
-> useEffect도 함수 형태로 써줘야 함! 뒤에는 빈 배열 하나 넣어줘야 함

-> 위와 같은 형태.

-> return 부분이 componentWillUnmount 역할
-> 두번째 배열에는 useEffect를 다시 실행할 값이 들어감! (즉, 위의 코드에서는 imgCoord가 바뀌면 useEffect를 실행한다.)

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

// 클래스의 경우 -> constructor -> render -> ref -> componentDidMount
// (setState/props 바뀔때) -> shouldComponentUpdate(true) -> render -> componentDidUpdate
// 부모가 나를 없앴을 때 -> componentWillUnmount -> 소멸

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

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

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



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

  useEffect(() => {
    interval.current = setInterval(changeHand, 100); //componentDidMount
    return () => {
      clearInterval(interval.current);
    }
  }, []);

  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 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, 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;

👑 useEffect와 useLayoutEffect

  • useEffect: 화면이 완전히 바뀌고나서 실행
  • useLayoutEffect: 화면 바뀌기 전에 발생되서, 화면 바뀌는 것을 감지

👑 custom hook

-> 특정한 hook 2개 이상이 반복된다. 그때 내가 직접 hook을 만들어 사용하는 것

👑 각종 지식

  • 메서드 안에 함수 호출하는 경우

    ->
    -> 빈 화살표를 함수 정의할 때 써줘야 함

  • state가 안쓰이는 것들은 class 밖으로 빼주기! class에 너무 많은 걸 묶으려고 하지 말자

  • 함수 컴포넌트는 렌더링 될 때마다 통째로 다시 실행

⭕ 나의 언어로 정리:
컴포넌트에는 라이프사이클이 존재한다. 즉, 컴포넌트 렌더링 전 후 로 처리해야할 것들을 위한 함수들이 있다.

  • class compoent에서:
    constructor(컴포넌트 처음 만들 때, state 초기화 위해서)-> render -> ref -> componentDidMount (처음 렌더링 한 직후 호출) -> state/props 변화, 부모 리렌더링 -> shouldComponentUpdate (true인 경우에 리렌더링 해줌) -> componentDidUpdate (업데이트 직 후 리렌더링 후 실행되는 함수) -> componetWillUnmount (컴포넌트 제거 직전에 호출)

  • 함수 컴포넌트 (hooks)에서:
    useEffect로 처리 (각 state들을 하나의 useEffect로 처리 가능), 두번째 배열 값은 그 useEffect를 다시 실행시키기 위한 조건의 값을 넣어준다./ useEffect는 함수 형태로, 그 안의 return의 값은 리렌더링 된 경우에 실행하는 것으로 이 또한 함수 형태로 나타낸다.

profile
지수의 취준, 개발일기
post-custom-banner

0개의 댓글