• 카테고리

    질문 & 답변
  • 세부 분야

    프론트엔드

  • 해결 여부

    해결됨

이번강의 코드의 동작결과를 바탕으로 예측한 동작순서가 맞는지 알고싶습니다.

22.04.14 12:11 작성 조회수 149

0

이번 강의를 보고 코드를 정리한 후 실행결과를 확인했습니다.

콘솔이 생각했던것 보다 7라인 더 찍혀서 궁금해서 질문 남깁니다. 물론 다음 강의에 useMemo나 useCallback으로 문제를 해결하겠지만, 제가 생각한 동작 순서가 맞는지 궁금합니다.

 

코드

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

function getWinNumbers() {
  console.log('getWinNumbers함수')
  const candidate = Array(45)
    .fill()
    .map((v, i) => i + 1)
  const shuffle = []
  while (candidate.length > 0) {
    shuffle.push(
      candidate.splice(Math.floor(Math.random() * candidate.length), 1)[0]
    )
  }
  const bonusNumber = shuffle[shuffle.length - 1]
  const winNumbers = shuffle.slice(0, 6).sort((p, c) => p - c)
  return [...winNumbers, bonusNumber]
}

const Lotto = () => {
  const [winNumbers, setWinNumbers] = useState(getWinNumbers())
  const [winBalls, setWinBalls] = useState([])
  const [bonus, setBonus] = useState(null)
  const [redo, setRedo] = useState(false)
  const timeouts = useRef([])

  useEffect(() => {
    console.log('useEffect1')
    for (let i = 0; i < winNumbers.length - 1; i++) {
      timeouts.current[i] = setTimeout(() => {
        setWinBalls(prevState => [...prevState, 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 둘 다 수행

  useEffect(() => {
    console.log('useEffect2 - 로또 숫자를 생성합니다.')
  }, [winNumbers])

  const onClickRedo = () => {
    console.log('onClickRedo')
    setWinNumbers(getWinNumbers())
    setWinBalls([])
    setBonus(null)
    setRedo(false)
    timeouts.current = []
  }

  return (
    <>
      <div>Win Numbers</div>
      <div id="결과창">
        {winBalls.map(v => (
          <Ball key={v} number={v} />
        ))}
      </div>
      <div>Bonus!</div>
      {bonus && <Ball number={bonus} />}
      {redo && <button onClick={onClickRedo}>One more</button>}
    </>
  )
}

export default Lotto

 

동작 결과 콘솔

11:46:54.502 Lotto.jsx:5 getWinNumbers함수

11:46:54.532 Lotto.jsx:28 useEffect1

11:46:54.532 Lotto.jsx:47 useEffect2 - 로또 숫자를 생성합니다.

11:46:55.951 Lotto.jsx:5 getWinNumbers함수

11:46:56.942 Lotto.jsx:5 getWinNumbers함수

11:46:57.729 Lotto.jsx:5 getWinNumbers함수

11:46:58.536 Lotto.jsx:5 getWinNumbers함수

11:46:59.535 Lotto.jsx:5 getWinNumbers함수

11:47:00.545 Lotto.jsx:5 getWinNumbers함수

11:47:01.536 Lotto.jsx:5 getWinNumbers함수

 

예측한 동작 순서

1. 함수 컴포넌트에 있는 winNumbers가 getWinNumbers 함수 호출하면서 getWinNumbers함수속 console 출력 -> 'getWinNumbers함수'

2. 27번줄의 useEffect의 두번째 인자인 timeouts.current가 [] 이기 때문에 첫번쨰 useEffect의 console 출력 -> 'useEffect1'

3. 46번줄의 useEffect의 두번째 인자인 winNumbers가 7개의 배열요소가 들어있기 때문에 true 이다 그래서 두번째 useEffect의 console 출력 -> 'useEffect2 - 로또 숫자를 생성합니다.'

4. useEffect1이 출력될때 아래 코드가 6번 동작해서 getWinNumbers함수 속에있는 console  출력 -> 'getWinNumbers함수' x 6

 for (let i = 0; i < winNumbers.length - 1; i++) {

      timeouts.current[i] = setTimeout(() => {

        setWinBalls(prevState => [...prevState, winNumbers[i]]) 

      }, (i + 1) * 1000)

    }

 

그리고 아래 코드가 한번 동작해서  getWinNumbers함수 속에있는 console  출력 -> 'getWinNumbers함수'

    timeouts.current[6] = setTimeout(() => {

      setBonus(winNumbers[6])

      setRedo(true)

    }, 7000)

 

동작 순서 4번에서 (setWinBalls)와 (setBonus + setRedo)가 비동기함수 라서 앞선 콘솔들이 찍히고 마지막에 찍혔던 거죠?

 

 

 

답변 1

답변을 작성해보세요.

1

네 맞습니다. 참고로 useEffect는 deps와 상관없이 무조건 처음 한 번은 실행됩니다.

띄융님의 프로필

띄융

질문자

2022.04.14

네네 다음강의를 보고 나니 useEffect 가 class 의 componentDidMount 역할을 유사하게 하는것처럼 무조건 한번은 실행된다는걸 이해했습니다!

 

제가 생각한 코드 순서가 맞다니까 다행이네요. 강의 이해가 너무 잘됩니다. 감사합니다~!