React

[React] 공식문서 HOOK 4 ~ 8정리

Alexim 2022. 9. 6. 17:36

Effect Hook

  • side effect를 수행할 수 있다.

Side Effect란?

  • 데이터 가져오기
  • 구독(subscription) 설정하기
  • 수동으로 React 컴포넌트 DOM 수정하기
  • 결과를 예상할 수 없는 것
  • 렌더링 과정에서 구현할 수 없는 작업
  • 다른 컴포넌트에 영향을 줄 수 있는 작업

정리(Clean-up)를 이용하지 않는 Effects

  • 네트워크 리퀘스트, DOM 수동 조작, 로깅 등은 정리가 필요 없는 경우들이다. 이러한 예들은 실행 이후 신경 쓸 것이 없기 때문

useEffect가 하는 일

  • 컴포넌트가 렌더링 이후에 어떤 일을 수행해야하는 지를 말한다.
  • useEffect 안의 함수는 DOM 업데이트를 수행한 이후에 불러낸다.

useEffect를 컴포넌트 안에서 불러내는 이유

  • effect를 통해 state 변수 또는 prop에 접근할 수 있게 된다.
  • 함수 범위 안에 존재하기 때문에 특별한 API 없이도 값을 얻을 수 있는 것

useEffect 렌더링 순서

  • 기본적으로 첫번째 렌더링과 이후의 모든 업데이트에서 수행된다.
  • 마운팅과 업데이트라는 방식으로 생각하는 대신 effect를 렌더링 이후에 발생하는 것으로 생각하는 것이 더 쉬울 것
  • 컴포넌트를 렌더링할 때 React는 이용한 effect를 기억하였다가 DOM을 업데이트한 이후에 실행한다.

대부분의 effect는 동기적으로 실행될 필요가 없다.

  • 흔하지는 않지만 동기적 실행이 필요한 경우에는 useEffect와 동일한 API를 사용하는 useLayoutEffect라는 별도의 Hook이 존재한다. (먼저 useEffect를 사용해 보고 문제가 있다면 그 다음으로 useLayoutEffect를 사용해 보기를 권한다.)

정리(clean-up)를 이용하는 Effects

  • 외부 데이터에 구독(subscription)을 설정해야 하는 경우에는 메모리 누수 가 발생하지 않도록 정리하는 것은 매우 중요하다.

effect에 정리가 필요한 경우에는 함수를 반환한다.

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
import React, { useState, useEffect } from 'react';

function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // 구독
    // effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다.
    return function cleanup() {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

effect에서 함수를 반환하는 이유?

  • 이는 effect를 위한 추가적인 정리 메커니즘. 모든 effect는 정리를 위한 함수를 반환할 수 있다. 이 점이 구독의 추가와 제거를 위한 로직을 가까이 묶어둘 수 있게 한다.

React가 effect를 정리하는 시점은 언제?

  • React는 컴포넌트가 마운트 해제되는 때에 정리를 실행한다.
  • 위의 예시에서 보면 effect는 한 번이 아니라 렌더링이 실행되는 때마다 실행된다. React가 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect 또한 정리하는 이유는 메모리 누수나 충돌이 발생하는 것을 방지하기 위함이다.

Effect를 건너뛰어 성능 최적화하기

  • 모든 렌더링 이후에 effect를 정리하거나 적용하는 것이 때때로 성능 저하를 발생시키는 경우도 있다.
  • 클래스 컴포넌트의 경우에는 componentDidUpdate에서 prevPropsprevState와의 비교를 통해 이러한 문제를 해결할 수 있다.
    componentDidUpdate(prevProps, prevState) {
    if (prevState.count !== this.state.count) {
      document.title = `You clicked ${this.state.count} times`;
    }
    }
  • useEffect에는 이러한 기능이 내장되어 있다. useEffect의 선택적 인수인 두 번째 인수로 배열을 넘기면 된다.

useEffect에서 의존성 배열을 사용할 때 주의해야 할 점

  • 배열이 컴포넌트 범위 내에서 바뀌는 값들과 effect에 의해 사용되는 값들을 모두 포함해야 한다. 그렇지 않으면 현재 값이 아닌 이전의 렌더링 때의 값을 참고하게 된다.

Hook 규칙

  • 최상위에서만 Hook을 호출해야 한다.
  • 오직 React 함수 내에서 Hook을 호출해야 한다. (또는 Custom Hook)

자신만의 Hook 만들기

사용자 정의 Hook 추출하기

  • 두 개의 자바스크립트 함수에서 같은 로직을 공유하고자 할 때는 또 다른 함수로 분리한다. 컴포넌트와 Hook 또한 함수이기 때문에 같은 방법을 사용할 수 있다.
  • 사용자 정의 Hook은 이름이 use로 시작하는 자바스크립트 함수이다.
  • 같은 Hook을 사용하는 두 개의 컴포넌트는 state를 공유하지 않는다. 사용자 정의 Hook은 상태 관련 로직을 재사용하는 매커니즘이지만 사용자 Hook을 사용할 때마다 그 안의 state와 effect는 완전히 독립적이다.

useEffect의 콜백이 개별 사용자 입력의 결과이거나 flushSync로 래핑된 업데이트의 결과인 경우

  • 레이아웃 및 페인트 전에 동기적으로 실행된다.

useRef

  • useRef hook은 DOM ref 만을 위한 것이 아니다. "ref" 객체는 현재 프로퍼티가 변경할 수 있고 어떤 값이든 보유할 수 있는 일반 컨테이너이다. 이는 class의 인스턴스 프로퍼티와 유사하다.
  • useEffect 내부에서 쓸 수 있다.
  • 지연 초기화를 수행하지 않는 한, 렌더링 중이 ref 설정을 피해라. 일반적으로 이벤트 처리와 effect에서 ref를 수정하는 것이 좋다.

고비용의 객체를 지연해서 생성하는 법

  • useMemo를 사용하면 종속성이 동일한 경우 값비싼 계산을 메모할 수 있다. 그러나 힌트 역할을 할 뿐이며 계산이 다시 실행되지 않는다는 보장은 없다. 때로는 객체가 한 번만 생성되었는지 확인해야 한다.

1. 일반적인 사용 사례 : 초기 state를 만드는 데 비용이 많이 드는 경우

function Table(props) {
  // ✅ createRows()는 한 번만 호출됩니다
  const [rows, setRows] = useState(() => createRows(props.count));
  // ...
}
  • 무시된 초기 state를 다시 생성하지 않으려면 useState에 함수 컴포넌트를 전달할 수 있다.
  • React는 첫 번째 렌더링 중에만 함수 컴포넌트를 호출한다.

useRef() 초깃값을 다시 작성하고 싶지 않을 때

  • 예를 들어 명령형 class 인스턴스가 한 번만 생성되도록 하고 싶다면

    function Image(props) {
    // ⚠️ IntersectionObserver는 모든 렌더링에서 생성됩니다
    const ref = useRef(new IntersectionObserver(onIntersect));
    // ...
    }
  • useRefuseState와 같은 특수 함수 컴포넌트 오버로드를 허용하지 않는다. 대신 느리게 생성하고 설정하는 자체 함수 컴포넌트를 작성할 수 있다.

    function Image(props) {
    const ref = useRef(null);
    
    // ✅ IntersectionObserver는 한 번 느리게 생성됩니다
    function getObserver() {
      if (ref.current === null) {
        ref.current = new IntersectionObserver(onIntersect);
      }
      return ref.current;
    }
    
    // 필요할 때 getObserver()를 호출해주세요
    // ...
    }
  • 이렇게 하면 처음에 진정으로 필요할 때까지는 값비싼 개체를 만들지 않아도 된다.