useEffect, useMemo, useLayoutEffect 알아보기

룸잉·2023년 10월 26일
3

cs

목록 보기
7/13

📑 React Life Cycle

들어가며, 리액트의 생명 주기에 대해 아주 간단히 알아보도록 하자 !

  • Mount: 컴포넌트가 최초 실행될 때
  • Render
    • 컴포넌트 내의 엘리먼트 요소들(HTML, React 사용자 정의 태그 등)을 화면 상에 그리는 동작
    • 컴포넌트가 마운트된 후, 컴포넌트가 호출될 때
  • re-Render: 렌더링 동작을 다시 수행하는것
  • UnMount: 컴포넌트가 사라질 때

즉, 컴포넌트가 처음 나타날 때마운트, 그 이후 값이 변경되어 컴포넌트가 변경된 상태에서 호출될 때렌더링 이라고 부른다.


🔰 Class Component 생명주기

👀 Mount (마운트)

: 컴포넌트가 생성 시, 발생하는 생명주기

✔︎ constructor

  • 컴포넌트 생성자 메서드, 컴포넌트가 생성되면 가장 먼저 실행되는 메소드

✔︎ getDerivedStateFromProps

  • props로부터 파생된 state를 가져오는 메소드

✔︎ render

  • 컴포넌트를 렌더링하는 메소드

✔︎ componentDidMount

  • 컴포넌트가 마운트되면 호출되는 메소드

👀 Updating (업데이트)

: 컴포넌트 업데이트 시점에 호출되는 메소드

✔︎ getDerivedStateFromProps

  • 컴포넌트의 props나 state 변경 시 호출되는 메소드

✔︎ shouldComponentUpdate

  • 컴포넌트의 리렌더링 여부를 결정하는 메소드
    • React.memo와 유사
    • boolean 반환

✔︎ componentDidUpdate

  • 컴포넌트 업데이트 후 발생하는 메소드
    • 의존성 배열이 변할 때만 useEffect가 실행되는 것과 같은 맥락 !

👀 UnMount (언마운트)

: 컴포넌트가 화면에서 사라지는 것과 관련된 메소드

✔︎ componentWillUnmount

  • 컴포넌트 생성자 메서드, 컴포넌트가 생성되면 가장 먼저 실행되는 메소드


📑 useEffect()

  • 컴포넌트가 렌더링될 때 특정 작업(side effect)을 실행할 수 있도록 하는 Hook
    • side effect: 컴포넌트가 렌더링 된 이후, 비동기로 처리되어야 하는 부수적인 효과
      • ex. 함수 내부에서 함수 외부의 변수를 변하게 하는 액션
  • componentDidMount와 componentDidUpdatecomponentWillUnmount가 합쳐진 것 !

🔰 useEffect, 언제 작업을 처리하는가!

  1. component가 mount 됐을 때
  2. component가 unmount 됐을 때
  3. component가 update 됐을 때

⇒ 즉, 클래스형 컴포넌트에서 사용할 수 있었던 생명주기 메소드(componentDidMount, componentDidUpdate, componentWillUnmount)를 함수형 컴포넌트에서도 사용 가능해짐!


🔰 useEffect 구조

useEffect (function, deps)
  • function: 수행하고자 하는 작업

    • 리액트는 이 함수를 기억했다가, DOM 업데이트 이후 불러냄
    • function이 함수(a)를 return할 경우, 컴포넌트 unmount 시 함수(a) 다시 실행
  • deps : 의존성, 이 값에 의존하여 function 실행

    • 배열 형태/ 검사하고자하는 특정 값이나 빈 배열이 들어감.
    • 특정 값이 들어가는 경우: 컴포넌트가 mount될 때, 지정한 값이 업데이트될 때 useEffect 실행
    • 빈 배열이 들어가는 경우: 따로 의존성을 갖지 않게 되어, 컴포넌트 mount 시 한 번만 실행됨.

🔰 cleanup 함수

👀 기본 형태

useEffect(() => {
	... // 실행할 내용

	return () => {
		... // clenup
	}
}
  • cleanup 함수: 컴포넌트 unmount 시, 실행되는 함수
    • 컴포넌트가 사라질 때 호출되는 함수
    • 메모리 누수를 방지하여 메모리를 관리하거나 컴포넌트가 사라질 때, 수행할 작업들을 추가하기 위해 사용
    • 단, react 18 버전부터는 메모리 누수에 대한 경고가 사라짐

👀 예제

useEffect(() => {
	// 실행 함수
  const timer = setTimeout(() => {
  }, 3000);

	// clean-up 함수
  return () => { lock = true };

}, []);
  • 리렌더링 시, useEffect의 return 함수 실행
  • 실행된 함수가 useEffect의 내부 기능의 작동을 막음
  • setState나 setTimeout, API 요청과 같은 비동기 함수가 작동할 때 조건문을 걸어 unmount되지 않았을때만 실행할 수 있도록 함

🔰 effect 타이밍

  • useEffect로 전달된 함수는 컴포넌트 렌더링 - 화면 업데이트 - useEffect 실행 순서로 실행됨.
    • 즉, useEffect 실행은 최초 렌더링 이후에 일어남!!
  • 만약 화면을 다 그리기 전에 동기화를 시키고 싶은 경우?
    • useLayoutEffect()를 활용하자!
    • useLayoutEffect로 전달된 함수는 컴포넌트 렌더링 - useLayoutEffect 실행 - 화면 업데이트 순서로 실행되기 때문에, 화면 업데이트 전 동기화가 가능해짐.


📑 useLayoutEffect()

  • 사실상 useEffect()와 거의 동일한 역할을 하는 훅
  • 아래에 명시한 useLayoutEffect()의 선언/ 정의 방식 역시, useEffect()와 거의 동일한 모습을 확인할 수 있다.

🔰 useLayoutEffect 구조

useLayoutEffect (function, deps)

useLayoutEffect(() => {
	... // 실행할 내용

	return () => {
		... // clean-up
	}
}
  • 수행하고자 하는 작업인 function과 함수 실행 시점에 영향을 미치는 의존성 배열인 deps 를 매개 변수로 가짐.
  • clean-up 함수 활용 가능.

🔰 useLayoutEffect() vs useEffect()

👀 이벤트 호출 시기

🐝 useEffect: 컴포넌트 마운트 실행 → 브라우저가 화면에 DOM 그리기(화면 업데이트) → effect 함수가 실행됨

🐿️ useLayoutEffect: 컴포넌트 마운트실행 → effect 함수 실행 → 브라우저 화면에 DOM 그리기(화면 업데이트)


👀 화면 깜빡임

🐝 useEffect: 컴포넌트들이 render, paint된 후 실행되기 때문에, 비동기적으로 실행이 이루어짐

  • 함수 내부 DOM에 영향을 주는 코드가 있을 경우, 화면 깜빡임 발생 ⭕️ !
    • paint: 실제 스크린에 Layout을 표시하고 업데이트하는 과정

🐿️ useLayoutEffect: 컴포넌트들이 render된 후 실행되고, 후에 pain가 되기 때문에 동기적으로 실행이 이루어짐

  • 함수 내부 DOM을 조작하는 코드가 존재하더라도, 화면 깜빡임 발생 ❌ !

👀 결론

  • useLayoutEffect()는 동기적으로 실행되며, 내부 코드가 모두 실행된 후 painting 작업을 거치기 때문에 로직이 복잡할 경우 사용자가 레이아웃을 보기까지 오랜 시간이 걸린다.
  • data fetch, event handler, state reset 등 **기본적인 작업 시에는 useEffect()를 사용**하는 것이 좋다.
  • 단, DOM을 조작하거나 state들이 조건에 따라 첫 painting 시 다르게 렌더링되어야 하는 경우에는 useLayoutEffect()를 사용하는 것이 좋다.
    • useEffect 사용 시, re-rendering으로 인해 화면 깜빡임 발생

📑 useMemo()

  • 리액트에서 컴포넌트 성능을 최적화하는데 사용되는 훅
    • 부모 컴포넌트의 값이 변했을 때, 자식 컴포넌트에 발생할 불필요한 리렌더링을 방지
  • useMemo에서 ‘memo’는 memoization을 의미
    • ‘메모리에 넣는다’는 의미
    • 컴퓨터 프로그램이 동일한 계산을 반복해야할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술

즉, 동일한 값을 반환하는 함수를 반복적으로 호출해야 한다면, 처음 값을 계산할 때 해당 값을 메모리에 저장하여 필요할 때마다 다시 계산하지 않고 메모리에서 꺼내서 사용


🔰 useMemo 구조

useMemo(calculateValue, dependencies)
  • calculateValue: 캐시하려는 값을 계산하는 함수
    • 인수를 취하지 않는 순수 함수여야 함!
  • dependencies: 의존성 배열
    • 특정 값이 들어가는 경우: 콜백 함수 재 호출 → memoization된 값 업데이트 → 다시 memoization 실행
    • 빈 배열이 들어가는 경우: 따로 의존성을 갖지 않게 되어, 컴포넌트 mount 시 한 번만 값 계산 → 이후 항상 memoization된 값 꺼내와서 사용

🔰 useMemo 주의사항

  • useMemo()를 활용하여 값을 캐싱하려면 구성 요소의 최상위 레벨에서 호출해야 함!
  • restrict mode에서는 계산 함수를 2번 호출
    • 이는 개발 전용 동작이며, 프로덕션에는 영향을 주지 않음!
    • 계산 함수가 순수함수로 잘 구현되어 있다면, 프로덕션에 영향을 주지 않고 호출 중 하나의 결과는 무시됨.
  • 값을 재활용하기 위해 메모리를 써서 값을 저장하는 훅이기 때문에, 불필요하게 모든 값을 memoization 해버리면 성능 저하가 발생하게 됨
  • 따라서, 꼭 필요한 경우에만 사용할 것을 권장한다 !

🚨 useMemo vs React.memo

🎃 React.memo

const MyComponent = React.memo((props) => {
	return (/*컴포넌트 렌더링 코드*/)}
);
  • Higher-Order Components(HOC)
    • 컴포넌트를 인자로 받아 새로운 컴포넌트를 다시 return 해주는 함수.
    • 인자로 받은 컴포넌트로 새로운 별도의 컴포넌트를 만듦.
  • 불필요한 컴포넌트 렌더링 방지.
    • 컴포넌트가 같은 props를 받고 같은 결과를 렌더링하는 경우, React.memo를 활용하여 불필요한 컴포넌트 렌더링 방지.
  • 오직 props 변경 여부만을 체크.
    • 함수 내부에서 useState와 같은 훅을 사용하고있다면, state가 변경될 때마다 렌더링 발생.
    • props로 들어온 값 비교. (num, str → 실제 값이 동일한지 비교, object → 참조 reference 여부 비교)
  • 메모이제이션을 하고자 하는 컴포넌트 자체를 React.memo로 감싸줌.
  • 상위 컴포넌트가 리렌더링되더라도 React.memo로 감싸준 컴포넌트는 리렌더링되지 않음.

🎃 useMemo

  • 복잡한 결과 값을 memoization 해서 최척화하기 위한 Hook.
  • Hook이기 때문에 함수형 컴포넌트 내에서만 사용 가능.
  • 값을 계산하는 과정을 최적화 → 값 반환.

🎃 결론

공통점

  • props가 변하지 않으면(이전 props와 동일하면) 인자로 넘긴 함수는 재실행되지 않고, 이전 memoized된 결과를 반환함.

차이점

  • React.memo는 HOC, useMemo는 hook.
  • React.memo: 함수이기 때문에 클래스형 컴포넌트와 함수형 컴포넌트 모두 사용 가능.
  • useMemo: hook이기 때문에 오직 함수형 컴포넌트 내에서만 사용 가능.

6개의 댓글

comment-user-thumbnail
2023년 10월 28일

좋은 글 잘 봤습니다 !!! 알고 있다고 생각했던 부분도 다시 한번 읽어보니 새로운 부분이 많네요 ㅎㅎ
저는 유튜브도 많이 참고하며 공부하는 편인데, 이 영상도 도움이 많이 되더라고!! 이해도 잘되고 ㅎㅎ
링크 같이 첨부해둡니다 🔥https://youtu.be/kyodvzc5GHU?si=9eH02h3qVczNmoKd
useLayoutEffect는 이번 아티클을 통해 처음 알게 되었어요! 상황에 맞게 useLayoutEffect도 잘 활용해 봐야겠다는 생각이 들었습니다.

추가적으로 저는 React를 공부할때 항상 공식문서를 봐요 ! 해당하는 내용의 공식문서도 함께 남겨둘테니 확인해보는 것도 도움이 될 수 있을 거 같습니다 😊
https://ko.legacy.reactjs.org/docs/hooks-effect.html

다시 한번 좋은 글 감사합니다 !

답글 달기
comment-user-thumbnail
2023년 10월 29일

렌더링과 마운트에 대한 내용과 useEffect vs useLayoutEffect를 이 잘 정리해주셔서 이해가 잘 됐습니다!
cleanup을 사용하는 경우를 조금 더 자세히 알아봤는데 언마운트 이전/ update 직전 사용한다고 하고 작동 순서는
re-render → 이전 effect clean up 함수 → effect순서로 작동한다고 하네요
컴포넌트가 unmount되면 비동기 효과를 정리하는 것이 좋다고 하는데 비동기 side effect가 prop또는 state 값에 따라 달라지는 경우도 컴포넌트가 업데이트 될때 이를 clean up함수로 정리하는 것을 고려해보는 것이 좋다고 합니다.
https://hackernoon.com/cleanup-functions-in-reacts-useeffect-hook-explained

1개의 답글
comment-user-thumbnail
2023년 10월 29일

정말 세세하게 하나하나 다루어 주셔서 감사합니다 ! 특히 useMemo와 React.memo의 차이점, 그리고 useLayoutEffect에 대해 설명해주신 부분은 제가 잘 몰랐던 부분이라 새롭게 알아가네요옹

저는 useLayoutEffect에 대해 더 알아보았어요! 공식문서에서도 useLayoutEffect은 성능 저하를 일으킬 수 있으니 useEffect 사용을 권장한다고 하여,, 그렇다면 이걸 도대체 정확히 어느 경우에 쓰는거지 ? 라는 의문이 생기더라구요,,
찾아본 결과 ! 보통의 컴포넌트의 경우에는 render시에 정확한 위치와 크기를 몰라도 된다고 해요 그저 JSX를 리턴하고 브라우저에서 위치랑 크기를 계산한 후 repaint되는 과정을 거치게 돼요!
하지만,,레이아웃 요소들 중에서 정확한 위치값을 사용하여 렌더링 하는 경우에 useLayoutEffect를 사용할 수 있다고 하네요 !
공식문서에서 예로 드는 경우는, 호버시 호버 한 element 바로 옆에 나타나는 tooltip이 있다고 가정해봅시다. 이때 레이아웃 상에서 공간이 적당히 있다면 element위에 tooltip이 나타나겠지만, 공간이 적당하지 않다면 tooltip이 element의 밑에 나타나게 되겠죠? 때문에 처음부터 tooltip을 정확한 위치에 위치시키기 위해서는 tooltip의 높이값을 알고 있어야 하고 그에 따른 위치를 useLayoutEffect를 사용하여 지정하면 브라우저가 repaint하기 전에 위치를 정하게 되므로 불필요하게 두 번 tooltip을 위치시키게 되지 않는다고 합니당
(useEffect를 사용해서 위치시키면 공간이 부족한 경우 처음에는 tooltip이 hover한 element의 위에 나타났다가 flicker(깜박거림!)후에 위치가 아래로 조정되어 나타나는 현상이 나타난다고 해요 !)

공식문서의 부분을 번역해서 작성해둔 것이라 ! 더 정확한 예시를 코드와 함께 확인해보고 싶으시다면 아래 링크 들어가서 보시면 될 것 같습니당 😆
https://react.dev/reference/react/useLayoutEffect

답글 달기
comment-user-thumbnail
2023년 10월 29일

글 너무 알차고 좋았어요!! useEffect 부터 useMemo까지 익숙하지만 익숙하지 않은 hook들을 살펴 볼 수 있었던 시간이었던 것 같습니다!
특히 useMemo와 React.memo를 나누어서 공통점, 차이점으로 설명해주신 게 너무 좋았습니다
관련해서 조금 더 찾아보았는데요,

일단 memoization 상황에서는 어떤 함수를 어떤 매개변수와 함께 불렀는 지 여부로 캐싱을 진행한다고 합니다. 따라서, React.memo로 한 번 감싸주면 App이 리랜더링되더라도 감싸준 컴포넌트는 리랜더링되지 않는다고 합니다.
React.memo => 한 컴포넌트를 같은 프로퍼티로 리랜더링하는 경우가 빈번할 때 사용
즉, property가 계속 변경되는 컴포넌트를 React.memo 로 묶는다 => 계속 비교하는 과정만 추가됨
또한, 참조 타입 props가 들어 있는데 React.memo로 묶는다면?
참조 타입은 리랜더링될 때마다 새로운 주소에 할당되므로 React.memo는 주소값만 살펴보기 때문에 쓸데 없는 리랜더링이 더욱 일어나게 된다고 합니다!!

useMemo => 컴포넌트가 아닌 복잡한 계산의 결과 값을 memoization해 최적화하는 hook 따라서 useMemo는 컴포넌트가 아닌 값이기에 값을 계산하는 과정이 최적화되는 것!
useMemo는 또한 랜더링 과정 중에 발동됩니다. 따라서, 만약 useMemo가 오래 걸리는 작업을 잡고 있다? => 렌더링 과정 중 시간 소요가 늘어나고, 화면 구성도 늘어난다고 합니다. useMemo는 렌더링 작업 중에 시행되기 때문에 비동기 작업의 결과값이 적절히 반영된다고 할 수 없다고 해요!!

최적화 최적화,,, 정말 많이 들어봤지만 사실 실제에서는 쓴 경우가 0이라,,, 실습 기대하고 있겠습니다!

답글 달기
comment-user-thumbnail
2023년 10월 29일

와... 리액트 생명주기 함수와 useEffect, useMemo에 대해 정말 꼼꼼하게 정리해주셔서 너무 유익했습니다! 이렇게 정리해보니 함수와 hook이 참 많은데 각각을 완벽히 이해하기가 쉬운 일은 아니네요. 무척 헷갈립니다. ㅠㅠ
그 중에서도 저는 clean up 함수에 대해 가장 생소해서 좀 더 알아봤습니다. useEffect에서 return할 때 사용하는 함수라니... 저는 useEffect에서 무언가를 리턴해 본 적은 한번도 없는 것 같거든요..! clean up 함수는 useEffect hook을 사용할 때, 컴포넌트가 사라지는 시점(unmount)과 특정 값이 변경되기 직전(deps update 직전)에 실행하는 작업을 지정하는 함수라고 합니다. 이 clean up 과정을, 연이어 발생한 이벤트를 그룹으로 묶어서 처리하여 불필요한 재렌더나 로직의 재 수행을 막는 Debouncing 이라고도 하더라구요..! 참고자료
clean up은 치명적인 오류를 막기도 하는데요, 예를 들어 서버 통신에서의 promise가 resolve되기 전에 컴포넌트가 unmount되어버리면, useEffect에서 이미 unmount된 컴포넌트의 state를 업데이트하려고 하기 때문에 에러가 나게 됩니다. 이런 상황에서 unmount전에 반드시 실행되어야 할 일들을 return () => { //here }; 안에 제시함으로써 해결이 가능합니다. 정말 유용하네요...! 참고자료

답글 달기