본문 바로가기

FrontEnd

useEffect는 라이프사이클 훅이 아니다.

반응형

Myths about useEffect | Epic React by Kent C. Dodds

 

Myths about useEffect

Some common mistakes I see people make with useEffect and how to avoid them.

epicreact.dev

 

TLDR : useEffect는 deps의 상태들과 사이드이펙트를 동기화하기 위한 훅이다.

 

처음에 리액트를 학습하면 클래스 컴포넌트와 라이프사이클에 대해 배운다.

라이프사이클 함수는 보통 내 코드를 다른사람의 코드 안에 끼워넣는 방법이다.

훅의 라이프사이클

해당 정보를 의식하고 클래스 컴포넌트처럼 코딩하면 아래처럼 된다.

function DogInfo({dogId}) {
  const controllerRef = React.useRef(null)
  const [dog, setDog] = React.useState(null)
  function fetchDog() {
    controllerRef.current?.abort()
    controllerRef.current = new AbortController()
    getDog(dogId, {signal: controllerRef.current.signal}).then(
      (d) => setDog(d),
      (error) => {
        // handle the error
      },
    )
  }
  // didMount
  React.useEffect(() => {
    fetchDog()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])
  // didUpdate
  const previousDogId = usePrevious(dogId)
  useUpdate(() => {
    if (previousDogId !== dogId) {
      fetchDog()
    }
  })
  // willUnmount
  React.useEffect(() => {
    return () => {
      controllerRef.current?.abort()
    }
  }, [])
  return <div>{/* render dog's info */}</div>
}
function usePrevious(value) {
  const ref = useRef()
  useEffect(() => {
    ref.current = value
  }, [value])
  return ref.current
}

 

하지만 실제로 이런 식으로 코딩하지 않는다.

Ryan Florence 

 

Ryan Florence on Twitter

“@dan_abramov @_developit @mjackson The question is not "when does this effect run" the question is "with which state does this effect synchronize with" useEffect(fn) // all state useEffect(fn, []) // no state useEffect(fn, [these, states])”

twitter.com

윗 분은 이렇게 말했다 한다.

 

"어떤 상태와 사이드이펙트를 동기화할 것인가"가 핵심이다.

useEffect(fn) // all state

useEffect(fn, []) // no state

useEffect(fn, [these, states])

그러면 아래와 같이 변경할 수 있다.

function DogInfo({dogId}) {
  const [dog, setDog] = React.useState(null)
  React.useEffect(() => {
    const controller = new AbortController()
    getDog(dogId, {signal: controller.signal}).then(
      (d) => setDog(d),
      (error) => {
        // handle the error
      },
    )
    return () => controller.abort()
  }, [dogId])
  return <div>{/* render dog's info */}</div>
}

useEffect를 주의하여 사용하기

  1. 의존성 배열 사용 조심하기.
    1. 변경 시 동기화해야 하는 상태를 배열에 명시적으로 나열한다.
    2. 성능 문제는 useCallback을 고려한다.

2. useEffect쪼개기

  1. 상태 - 사이드이펙트의 연관관계를 잘 파악하여 쪼갠다.
function ChatFeed() {
	// 사이드이펙트 - 상태 관계를 잘 맺어두어 분리해두면 나중에 분리하기 쉬워진다.
  React.useEffect(() => {
    // subscribe to feed
    return () => {
      // unsubscribe from feed
    }
  })
  React.useEffect(() => {
    // set document title
    return () => {
      // restore document title
    }
  })
  React.useEffect(() => {
    // subscribe to online status
    return () => {
      // unsubscribe from online status
    }
  })
  React.useEffect(() => {
    // subscribe to geo location
    return () => {
      // unsubscribe from geo location
    }
  })
  return <div>{/* chat app UI */}</div>
}

// 커스텀 훅으로 뽑아내기
function ChatFeed() {
  // NOTE: this is pseudo-code,
  // you'd likely need to pass values and assign return values
  useFeedSubscription()
  useDocumentTitle()
  useOnlineStatus()
  useGeoLocation()
  return <div>{/* chat app UI */}</div>
}

 

3. useEffect 콜백 내에서 호출할 함수를 정의해야 하는 경우 외부가 아닌 내부에서 정의하라.

- 의존성 배열 관리가 어려워질 수 있음.

 

참고 : 컴포넌트는 원자고 훅은 전자다. - 댄 아브라모프 -

반응형