본문 바로가기
FrontEnd/React.js

[React.js] 제로초 웹게임 - 5. 가위바위보 (life Cycle, useEffect)

by Chaedie 2022. 10. 25.
728x90

강의를 통해 배운 점

useEffect에 대해 알고 있었고, 사용법 또한 알고 있었습니다. 프로젝트에서 문제없이 사용했고, 항상 의도한 대로 사용되어 왔습니다. 하지만 강의에서 오류가 생기는걸 보았고, 나는 문제가 없는데 강의에선 왜 문제가 생겼지? 라는 의문이 생겼습니다.

공식문서를 뒤져보니 강의에서의 설명이 맞았고 (rendering될 때마다 useEffect 콜백이 실행이 되고 clean-up또한 실행이 된다.는 내용) 이게 지금도 이해는 안가지만 어쨋든 공식문서에 나와 있는 것처럼 이럼에도 불구하고 문제가 안생긴것은 디펜던시 배열에 빌드 시 자동으로 추가 될 수 있기 때문이라는 것도 배웠습니다.

강의를 보고 나서 오히려 더 이해가 안가는 상황입니다. 지금 진짜 ~~ 이해 안되는 상황인데 일단은 넘어가야 되겠습니다. 다음에 다시 봐야할것 같아요. ㅠㅠ 

아마 React가 버젼 업이 되면서 자동으로 빌드 시 추가되는 걸로 바뀌어서 오류가 없는 것 같은데, 추론일 뿐입니다. ... 허허..

진짜 life Cycle이 훨씬 직관적이긴 하네요. 😭


내가 짠 코드

import React, { Component } from 'react';

class RSPClass extends Component {
  state = {
    aiPick: '',
    userPick: '',
    result: '',
  };

  i = 0;
  interval;

  componentDidMount = () => this.start();
  componentWillUnmount = () => clearInterval(this.interval);

  start = () => {
    clearInterval(this.interval);
    this.interval = setInterval(() => {
      this.setState({ aiPick: rsp[this.i % 3] });
      this.i++;
    }, 200);
    this.setState({ result: '' });
  };

  onClick = e => {
    const { aiPick, result } = this.state;
    if (result) return;

    clearInterval(this.interval);
    this.setState({ userPick: e.target.name, result: rspResult[e.target.name][aiPick] });
  };

  render() {
    return (
      <>
        <h1>가위 바위 보! - {this.state.result}</h1>
        <div
          className={'image'}
          style={{
            backgroundPosition: rspCoords[this.state.aiPick],
          }}
        ></div>
        {rsp.map(el => (
          <button key={el} onClick={this.onClick} name={el}>
            {el}
          </button>
        ))}
        <br />
        <button onClick={this.start}>다시 시작</button>
      </>
    );
  }
}

export default RSPClass;

const rsp = ['바위', '가위', '보'];
const rspCoords = {
  바위: '0',
  가위: '-142px',
  보: '-284px',
};
const rspResult = {
  바위: { 바위: '비겼다!', 가위: '이겼다!', 보: '졌다 😭' },
  가위: { 가위: '비겼다!', 보: '이겼다!', 바위: '졌다 😭' },
  보: { 보: '비겼다!', 바위: '이겼다!', 가위: '졌다 😭' },
};

i가 number의 최댓값보다 넘어가면 overflow 뜨겠네요! ㅎㅎ

componentDidMount() 일 때 this.start()를 호출하는건 잘해줬는데, componentWillUnmount() 일 때, clearInterval을 안해줬었습니다! 제로초 님 코드 보고 추가했어요!

클래스 컴포넌트의 라이프 사이클

import React, { Component } from 'react';

// 클래스 컴포넌트의 라이프 사이클
// 생성 : constructor => render => ref => componentDidMount
// 갱신 : => (state/props 바뀔 때 => shouldComponentUpdate(true) => render => componentDidUpdate)
// 제거 : 부모가 나를 없앴을 때 => componentWillUnmount => 소멸
class RSPClass extends Component {

  // 컴포넌트가 첫 렌더링 된 후, 여기에 비동기 요청을 많이 함
  componentDidMount = () => this.start(); 
  // 리렌더링 후
  componentDidUpdate() {} 
  // 컴포넌트가 제거되기 직전, 비동기 요청 정리를 많이 함
  componentWillUnmount = () => clearInterval(this.interval);

...

  render() {
    return (
      <>
...
          </>
    );
  }
}

export default RSPClass;

componentDidMount() ⇒ 컴포넌트의 첫 렌더링 후

componentDidUpdate() ⇒ 리렌더링 후

componentWillUnmount() ⇒ 컴포넌트가 제거되기 직전

주석만 잘 봐도 라이프 사이클은 다 이해할 수 있습니다. 굳!

강의 코드 해석

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.바위,
      });
    }
  };

저는 i라는 변수를 둬서 012 012 012 로 선택되도록 했는데요.

강의에선 changeHand라는 함수를 만들고, 이 함수는 지금의 값을 이용해 다음값을 정해주는 형태로 했네요.

React에서 많이 쓰이는 패턴

onClick = (pattern) => {
    return pattern
}

<div onClick={() => this.onClick('바위')}></div>

위 코드 중 아래 div 안과 같이 애로우 펑션이 있을 때, 아래 코드블럭과 같이 고쳐주면 같은 코드를 만들 수 있습니다.

onClick = (pattern) => () => {
    return pattern
}

<div onClick={this.onClick('바위')}></div>

이런걸 하이어 오더 펑션, 고차 함수 라고 하는데 실무에서 많이 쓰인다고 한다.

근데 솔찍히 이거 명확히 이해 안되는데 많이 보다 보면 이해 되겠지?

이해 안되는 점

데브 툴즈로 리렌더링 확인할 때 제로초님은 아래 버튼들은 쓸데없이 리렌더링 되지 않는다.

근데 난 왜 버튼들이 리렌더링이 되는거지?

이거 이상한게 아까부터 계속적으로 이런일이 발생해서 뭔가 문제가 있는것 같다.

Untitled

✅ 아쉽게도 버전 문제 때문에 하이라이트가 되는걸로 바뀌었다고 하네요. 왠지 아쉽네요. 그럼 데브툴즈를 사용해도 리렌더링을 정확히 캐치하지 못하는거네요.


함수 컴포넌트로 구현한 내 코드

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

function RSP() {
  const [aiPick, setAiPick] = useState('');
  const [userPick, setUserPick] = useState('');
  const [result, setResult] = useState('');

  const i = useRef(0);
  const interval = useRef();

  useEffect(() => {
    start();
    return () => {
      clearInterval(interval.current);
    };
  }, []);

  const start = () => {
    clearInterval(interval.current);
    interval.current = setInterval(() => {
      setAiPick(rsp[i.current % 3]);
      i.current++;
    }, 200);
    setResult('');
  };

  const onClick = e => {
    if (result) return;

    clearInterval(interval.current);
    setUserPick(e.target.name);
    setResult(rspResult[e.target.name][aiPick]);
    setTimeout(() => {
      start();
    }, 2000);
  };

  return (
    <>
      <h1>훅스 가위 바위 보! - {result}</h1>
      <div
        className={'image'}
        style={{
          backgroundPosition: rspCoords[aiPick],
        }}
      ></div>
      {rsp.map(el => (
        <button key={el} onClick={onClick} name={el}>
          {el}
        </button>
      ))}
      <br />
      <button onClick={start}>다시 시작</button>
    </>
  );
}

export default RSP;

const rsp = ['바위', '가위', '보'];
const rspCoords = {
  바위: '0',
  가위: '-142px',
  보: '-284px',
};
const rspResult = {
  바위: { 바위: '비겼다!', 가위: '이겼다!', 보: '졌다 😭' },
  가위: { 가위: '비겼다!', 보: '이겼다!', 바위: '졌다 😭' },
  보: { 보: '비겼다!', 바위: '이겼다!', 가위: '졌다 😭' },
};

바뀐게 거의 없습니다. 사실 클래스 컴포넌트, 함수 컴포넌트 둘 다 사용할 줄 안다면 거의 동일한 코드라고 봐도 될것 같습니다. 생활코딩에서 클래스 컴포넌트를 처음 만났을 땐 굉장히 당황스러웠었는데 이제는 그냥 “별 다를게 없다” 라는 생각이 드네요.

변경점 1) 단순 변수는 useRef를 사용했습니다.

변경점 2) 라이프사이클 메서드 대신 useEffect를 사용했습니다.

useEffect 강의 중

useEffect 강의 중 이상한 부분이 있던데 원인파악은 정확히 안됩니다. 일단 제 코드에서와 다르게 움직이기 때문에 .. 이해할 수 없고, 저는 의도한 대로 코딩을 했는데 문제 없이 진행이 되어서요.

setTimeout을 하고 return 에 클린업으로 clear해주는 코드를 짜셨는데 왜 바로 클린업이 되는지 모르겠네요.

Untitled

빌드 시 자동으로 dependency 배열에 추가되어서 강의랑 다르게 오류가 없었던 것 같은데, 이걸 확인할 방법이 없다. 웹팩에서 소스맵을 설정해서 직접 추가된 디펜던시를 보고싶었는데, 소스맵이 자꾸 안보여서 … 일단 오늘은 자러 갑니다.

댓글