React 스터디 5주차

RIHO·2022년 2월 18일

React STUDY

목록 보기
6/11
post-thumbnail

1. 라이프사이클

ComponentDidMount

  • 컴포넌트가 처음 실행되었을 때 실행
  • setState 등으로 리렌더링이 일어날 때는 실행되지 않음
  • 주로 비동기 요청을 함

ComponentWillUnmount

  • 컴포넌트가 제거되기 직전에 실행
  • 부모 컴포넌트가 자식 컴포넌트를 제거할 때에도 실행
  • 비동기 요청을 정리 (ComponentDidMount나 ComponentDidUpdate에서의 비동기 요청이 남아 있으면 메모리 누수와 같은 문제가 발생할 수 있으므로 ComponentWillUnMount에서 정리)

ComponentDidUpdate

  • 리렌더링 후에 실행

shouldComponentUpdate

  • 변화하는 것이 있을 때만 렌더링이 발생

2. 가위바위보 게임 (class)

import React, { Component } from 'react';

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

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

const computerChoice = (imgCoord) => { // computer가 어떤 손을 냈는지 판단
  return Object.entries(rspCoords).find(function(v) {
    return v[1] === imgCoord;
  })[0];
};

class RSP extends Component { 
  state = {
    result: '',
    imgCoord: '0',
    score: 0,
  }

  interval;

  // 라이프사이클
  // constructor -> render -> ref -> ComponentDidMount -> 
  // (setState/props 바뀔 때 -> shouldComponentUpdate -> render -> componentDidUpdate)
  // 부모가 나를 없앴을 때 -> componentWillUnmount -> 소멸

  componentDidMount() { // 컴포넌트 첫 렌더링 후 비동기 요청
    this.interval = setInterval(this.changeHand, 100)
    // 최초 1번만 실행.
    // 주로 비동기 요청
  }

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

  render() { 
    const { result, score, imgCoord } = this.state;
    // imgCoord : 이미지의 좌표
      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;

3. 가위바위보 게임 (Hooks)

Hooks는 라이프사이클을 가지고 있지 않으나, useEffect를 사용하여 비슷한 기능을 구현하는 것은 가능하다.
이때 useEffect는 렌더링 결과가 반영된 후 호출된다.

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

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

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

const computerChoice = (imgCoord) => { // computer가 어떤 손을 냈는지 판단
  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(() => {
    // compoentDidMount, componentDidUpdate 역할 (1:1 대응은 X)
    interval.current = setInterval(changeHand, 100);
    return () => {
      // componentWillUnmount 역할
      clearInterval(interval.current);
    }
  }, [imgCoord]); // useEffect를 실행하고 싶은 state. 여러 개 참조도 가능 
  // 하지만 꼭 useEffect를 다시 실행할 값만 넣어야 함
  // imgCoord가 바뀔 때마다 useEffect가 계속 실행됨

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

  const onClickBtn = (choice) => () => {
    // if (interval.current) {
      
    // }
    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);
    }, 1000);
  };

    // imgCoord : 이미지의 좌표
      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>
      </>
    );
}

4. class와 Hooks의 라이프사이클 비교

  • class의 경우, componentDidMount나 componentDidUpdate에서 모른 state를 조건문으로 분기 처리한다.
  • Hooks의 경우, useEffect가 state를 하나씩 처리한다.

추가

Cannot update during an existing state transition (such as within 'render'). Render methods should be a pure function of props and state.

Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

이를

onClickBtn = (choice) => {

{...}

}

로 적어두어서 오류가 났다. 😣 (캡처 코드가 정답)
함수 호출 -> render -> 함수 호출 -> render... 이런 식으로 무한루프에 빠져 발생하는 오류라고 한다.

이 코드를 changeHand()라고 적었을 때에도 동일한 오류가 발생하였다.

profile
Front-End / 기록용

0개의 댓글