본문 바로가기
FrontEnd/React.js

[React.js] 제로초 웹게임 - 6. 로또 (useCallback, useMemo)

by Chaedie 2022. 12. 1.
728x90

강의를 통해 배운 점

1달만에 강의 수강을 다시 시작했다. 그러다보니 클래스 컴포넌트 만드는게 살짝 잊혀져서 “어떻게하더라?” 라며 멍때리게 되더라. 노션에 메모 해둔 시리즈를 보면서 다시 기억을 되살릴 수 있었는데, 정말 노션과 블로깅은 개발자에게 필수인것 같다. 한번 습득했던 기술은 언제든 기억에서 사라질 수 있고, 그럴 때 기록했던 내용을 보면서 복기하면 다시 기억을 되살릴 수 있다는걸 깨달았다.

노션 기록과 블로깅이 꽤나 귀찮은 일이지만 최대한 열심히 해야함을 제대로 느꼈다.


클래스 컴포넌트 (강의코드)

import React, { Component } from 'react';
import Ball from './Ball';
import { getWinNumbers } from './getWinNumbers';

class LottoClass extends Component {
  state = {
    winNumbers: getWinNumbers(), // 당첨 숫자들
    winBalls: [],
    bonus: null, // 보너스 공
    redo: false,
  };

  timeouts = [];

  runTimeouts = () => {
    const { winNumbers } = this.state;
    for (let i = 0; i < winNumbers.length - 1; i++) {
      this.timeouts[i] = setTimeout(() => {
        this.setState(prevState => {
          return { winBalls: [...prevState.winBalls, winNumbers[i]] };
        });
      }, (i + 1) * 100);
      this.timeouts[6] = setTimeout(() => {
        this.setState({
          bonus: winNumbers[6],
          redo: true,
        });
      }, 700);
    }
  };
  componentDidMount() {
    this.runTimeouts();
  }

  componentDidUpdate() {
    if (this.state.winBalls.length === 0) {
      this.runTimeouts();
    }
  }

  componentWillUnmount() {
    this.timeouts.forEach(x => clearTimeout(x));
  }

  onClickRedo = () => {
    this.setState({
      winNumbers: getWinNumbers(), // 당첨 숫자들
      winBalls: [],
      bonus: null, // 보너스 공
      redo: false,
    });
    this.timeouts = [];
  };

  render() {
    const { winBalls, bonus, redo } = this.state;
    return (
      <>
        <div>당첨숫자</div>
        <div id="결과창">
          {winBalls.map(x => (
            <Ball key={x} number={x} />
          ))}
        </div>
        <div>보너스!</div>
        {bonus && <Ball number={bonus} />}
        {redo && <button onClick={this.onClickRedo}>한번 더!</button>}
      </>
    );
  }
}

export default LottoClass;

함수 컴포넌트 (내 코드)

import React, { useEffect, useRef, useState } from 'react';
import Ball from './Ball';
import { getWinNumbers } from './getWinNumbers';

function LottoHooks() {
  const [winNumbers, setWinNumbers] = useState(getWinNumbers);
  const [winBalls, setWinBalls] = useState([]);
  const [bonus, setBonus] = useState(null);
  const [redo, setRedo] = useState(false);

  const timeouts = useRef([]);
  const redoFlag = useRef(false);

  const runTimeOuts = () => {
    for (let i = 0; i < 6; i++) {
      timeouts.current[i] = setTimeout(() => {
        setWinBalls(prev => [...prev, winNumbers[i]]);
      }, (i + 1) * 100);
    }
    timeouts.current[6] = setTimeout(() => {
      setBonus(winNumbers[6]);
      setRedo(true);
    }, 700);
  };

  useEffect(() => {
    runTimeOuts();
    return () => timeouts.current.forEach(x => clearTimeout(x));
  }, [redoFlag.current]);

  const onClickRedo = () => {
    setWinNumbers(getWinNumbers);
    setWinBalls([]);
    setBonus(null);
    setRedo(false);
    redoFlag.current = !redoFlag.current;
  };

  return (
    <>
      <div>당첨숫자 훅스</div>
      <div id="결과창">
        {winBalls.map(x => (
          <Ball key={x} number={x} />
        ))}
      </div>
      <div>보너스!</div>
      {bonus && <Ball number={bonus} />}
      {redo && <button onClick={onClickRedo}>한번 더!</button>}
    </>
  );
}

export default LottoHooks;

같은 로직이라도 함수 컴포넌트가 훨씬 깔끔하고 좋다. 아무래도 계속 함수 컴포넌트로 개발을 하다보니 훨씬 편하게 느낄지도 모르겠다. 어쨌든 useEffect로 시작시점과 끝시점, 업데이트 시점을 정해줄수 있다보니 훨씬 보기가 쉬워졌다.

개선 코드 1 : useEffect

useEffect(() => ... , [timeouts.current]

useEffect의 디펜던시로 플래그를 줬었는데, 타임아웃츠 또한 새로 시작할 때마다 새로운 [] 배열이 생성되는 거라 같은 역할을 한다. 이를 업데이트 캐치용 디펜던시로 세팅해주면 정상 동작한다.

개선 코드 2 : useMemo, useCallback

사실 필히 개선할 사항은 없어서 코드는 추가하지 않는다. 대신 기록만 남김

1) useMemo, useCallback 모두 메모이제이션을 활용해 return value, return 함수 가 이전과 같으면 ([]디펜던시 배열안의 값이 안바뀌면) 새로 만들지 않는다.

2) 가장 필요한 경우는 Props를 던질때 쓸데없이 새로 생성된 함수 때문에 하위 컴포넌트가 리렌더링이 되는 경우가 있다. 이를 막기 위해 useCallback의 사용이 꼭 필요해진다.

3) useMemo의 경우는 복잡한 연산을 굳이 리렌더링 될때마다 연산하지 않도록 저장해두면 유용하다.

훅스 자잘한 팁들

1) 훅스는 최상위에 나둬야한다. (if, for, 다른 훅스 안에서 사용하지 않기)

댓글