공부기록/웹 개발

[웹 게임을 만들며 배우는 React] 6장

_우지 2022. 8. 29. 17:16

반복문을 기점으로 컴포넌트를 쪼개기

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

 

setTimeout을 배열에 넣어서 한꺼번에 unmount

  runTimeouts = () => {
    console.log('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) * 1000);
    }
    this.timeouts[6] = setTimeout(() => {
      this.setState({
        bonus: winNumbers[6],
        redo: true,
      });
    }, 7000);
  };

  componentDidMount() {
    console.log('didMount');
    this.runTimeouts();
    console.log('로또 숫자를 생성합니다.');
  }
  
    componentWillUnmount() {
    this.timeouts.forEach((v) => {
      clearTimeout(v);
    });
  }

 

ComponentDidUpdate

클래스 컴포넌트에서는 ComponentDidUpdate 를 사용해서 업데이트를 관리한다.

prevState에는 바뀌기 이전 state 가 저장되어있다.

ComponentDidUpdate 는 매번 실행되므로 if 문으로 분기처리를 잘해주어야한다.

  componentDidUpdate(prevProps, prevState) {
    console.log('didUpdate');
    if (this.state.winBalls.length === 0) {
      this.runTimeouts();
    }
    if (prevState.winNumbers !== this.state.winNumbers) {
      console.log('로또 숫자를 생성합니다.');
    }
  }

 

함수형 컴포넌트에서 useRef

timeouts 라는 변수에 useRef([]) 로 선언을 해주면 timeouts.current[i] 처럼 배열에 저장을 할 수 있게 된다.

const Lotto = () => {
  ...
  const timeouts = useRef([]);

  useEffect(() => {
    console.log('useEffect');
    for (let i = 0; i < winNumbers.length - 1; i++) {
      timeouts.current[i] = setTimeout(() => {
        setWinBalls((prevBalls) => [...prevBalls, winNumbers[i]]);
      }, (i + 1) * 1000);
    }
    timeouts.current[6] = setTimeout(() => {
      setBonus(winNumbers[6]);
      setRedo(true);
    }, 7000);
    return () => {
      timeouts.current.forEach((v) => {
        clearTimeout(v);
      });
    };
  }, [timeouts.current]); // 빈 배열이면 componentDidMount와 동일
  // 배열에 요소가 있으면 componentDidMount랑 componentDidUpdate 둘 다 수행

 

 

useCallback 을 꼭 사용해야할때는

자식 컴포넌트에 props 넘길때, 자식은 부모의props 가 업데이트 되면 다시 렌더링 되기 때문에 useCallback 을 처리를 꼭 해줘야한다.

 

만약 함수형 컴포넌트에서 componentDidUpdate 만 하고 싶다면?

다음 패턴을 사용하면 된다.

const mounted = useRef(false);
useEffect(()=> {
	if (!mounted.current){
    	mounted.current = true;
    } else {
    	
    }
},[]);

여기서 잠깐

state 가 변경될때에 화면이 리렌더링이 된다. 이렇게 어떠한 변수를 사용하고 싶을때 리렌더링 되는 것이 문제가 된다면,

const mounted = useRef(false) 와 같이 useRef 를 사용할 수 있다.

그리고 값을 변경하고 싶을때는 mounted.current = true 와 같은 방법으로 값을 할당해주면 된다.

 

useState 에서 일급함수 사용

function getWinNumbers() {
 	…
}
const Lotto = () => {
  const [winNumbers, setWinNumbers] = useState(getWinNumbers());
}
function getWinNumbers() {
 	…
}

const Lotto = () => {
  const [winNumbers, setWinNumbers] = useState(getWinNumbers);
}

다음과 같은 2개의 코드가 있을때 첫번째 코드는 리렌더를 될때마다 함수가 실행되었다.

두번째 코트는 그렇지 않고 한번만 실행되는 이유가 궁금했다.

 

첫번째 코드 같은 경우에는 의도적으로  getWinNumbers() 를 실행하였으므로 리렌더 될때마다 함수가 실행되는 것은 당연한 것이였다.

두번째 코드는 useState(getWinNumbers) 이렇게 함수를 넘겻을때는 useState가 처음에만 함수를 호출한다.

 

실행한 값을 전달하느냐 , 함수 자체를 전달하느냐의 차이이다.

 

또한 두번째 코드는 다음의 useMemo를 사용한 코드와 같은 역할을 한다.

function getWinNumbers() {
 	…
}
const Lotto = () => {
  const lottoNumbers = useMemo(()=> getWinNumbers(), [])
  const [winNumbers, setWinNumbers] = useState(lottoNumbers);
}

https://codesandbox.io/s/wonderful-surf-cpysoc?file=/src/Lotto.jsx 

 

wonderful-surf-cpysoc - CodeSandbox

wonderful-surf-cpysoc by ehddud1006 using react, react-dom, react-scripts

codesandbox.io