[React] Hooks API 파헤쳐보기

hyeondoonge·2023년 12월 12일
0

Hooks API

함수형 컴포넌트를 작성할 때 React 에서 제공하는 다양한 기능을 선언적으로 이용할 수 있도록 하는 API이며 상태를 관리, 컴포넌트의 생명주기에 접근하는 등의 작업을 할 수 있다.

Rule

  • 컴포넌트 내 최상단 블록에서 호출 (⇒ 조건문/루프/중첩 함수 내부에서 호출 X)
    • 컴포넌트가 렌더링 될 때, hook이 항상 동일한 순서로 호출되게 한다.
    • 여러 번의 useState, useEffect 호출 사이에서 hook의 상태를 올바르게 보존할 수 있다.
    • linter plugin을 통해 룰 관련 오류 표시를 자동화할 수 있다.
  • React 함수 내에서 호출
    • hooks api는 React 함수 컴포넌트 또는 Custom hook에서 호출할 수 있다.

State

useState

  • 컴포넌트 상태 관리
  • 상태를 조회하고 업데이트할 수 있음
  • 상태업데이트 시 리렌더링이 호출됨

useReducer

  • reducer를 이용한 컴포넌트 상태 관리
  • reduce, dispatch, action로 구성

Ref

useRef

  • 컴포넌트의 생명주기 동안 동일한 객체를 반환한다.
  • 단일 프로퍼티(current)를 가진 일반 자바스크립트 객체를 반환하며, React는 사용자가 언제 변경했는지 알지 못한다.
  • 리렌더링을 트리거하지 않는 데이터이며, 컴포넌트 외형 변화에 직접적인 영향 주지 않는 경우 활용할 수 있다.

사용 사례 1: Timer

timer는 리액트 컴포넌트 렌더링에 영향 미치지 않기 때문에 상태로 관리하면 불필요한 리렌더링 발생. 일반 변수로하면 상위 리렌더링 시 다른 값 가르켜서 정상적인 동작 불가함 ⇒ ref 사용!

   function Timer (callback) {
   	const timer = useRef(setTimeout(() => (
   	// 다른 상태에 의해 재렌더링 되더라도 같은 참조를 가리키며, 리렌더링 발생하지 않음.
   			callback();
   	), 2000));
   }

사용 사례 2: 변수

    export default function Counter() {
      let ref = useRef(0);

      function handleClick() {
        ref.current = ref.current + 1;
        alert('You clicked ' + ref.current + ' times!');
      }

      return (
        <button onClick={handleClick}>
          Click me!
        </button>
      );
    }

만약 {ref.current} 를 JSX에 작성하면 click을 하더라도 변경 내용이 반영되지 않는다. 리렌더링 트리거하지 않는 useRef의 특성때문이다.

사용 사례 3: DOM 노드 조작
reference가 가리키는 돔노드가 화면에서 사라지면 null로 세팅된다.

  • input focus, scroll dom api, 부모 컴포넌트의 DOM 조작 등 DOM API 사용

Effect

컴포넌트가 외부 시스템과의 연결 및 동기화하는 작업을 정의, React 코드가 아닌 코드를 실행하기위한 용도

이와 같이 side effect를 발생시킬 수 있는 로직들을 표현하는데 사용한다.

side effect란 함수 외부에 존재하는 값이나 상태를 변경시키는 행위이다. 따라서 일관된 결과를 보장해주지 못해 실행상태 예측을 어렵게하며 서비스의 복잡성을 증가시키는 요인이다.

useEffect

React서 effect는 서버와의 통신, 구독 설정 및 해제, 브라우저 dom 조작 등과 같은 작업을 말한다. 이는 첫 번째 렌더링 후, 모든 업데이트 후 모두 실행된다. 기본적으로 “렌더링 후”에 발생한다.

useEffect에는 의존성 배열을 전달할 수 있는데, 이 값이 변경됐다면 callback 호출한다. 만약 배열에 전달된 값 없으면 초기 호출되고, 아예 파라미터 전달이 되지 않았다면 매번 호출된다.

동시에 clean-up함수를 정의할 수 있다 있다. 이는 useEffect의 반환함수로, 리렌더링 직전과 언마운트 시점에 실행된다. 실제로 메모리 누수 방지를 위해 명시적으로 객체를 제거할 때 유용하게 사용될 수 있다. (ex. timer 종료, 바인딩된 이벤트 제거)

useLayoutEffect

useEffect는 화면에 그리는 작업까지 모두 마친 후 비동기적으로 실행되고 useLayoutEffect는 paint 이전 단계에서 동기적으로 실행된다는 차이가 있다. 이러한 특징때문에 메인 스레드가 블로킹되어 사용자가 성능 저하를 느낄 수 있게된다.

즉 UX 저하에 부정적인 영향을 줄 수 있는 API이기 때문에 꼭 필요한 상황에서만 사용하고 이외의 side effect들은 useEffect에서 수행하도록 해야한다.

하지만 useLayoutEffect를 통해 UX 향상할 수 있따. useEffect로 DOM 요소에 변화를 주게되면 화면에 깜빡임이 발생할 수 있는데 useLayoutEffect를 이용해 미리 렌더링 위치를 조정하면 깜빡임을 개선할 수 있다.

(ex. Paint 이전에 DOM 요소의 올바른 최종 layout를 결정할 경우)

사용 사례 1: 마우스 위치에 따른 툴팁 렌더링
사용 사례 2: 스크롤 위치에 다른 드롭다운 렌더링

Performance

렌더링 최적화를 위한 방법으로 불필요한 고비용 연산의 실행, 참조 동등성 보장을 통해 리렌더링 비용, useEffect 실행 비용등을 아낄 수 있다.

참조 동등성 보장을 통해 달성할 수 잇는 것은 불필요한 effect 호출 방지, 하위 컴포넌트 리렌더링 방지가 가능하다.

useCallback

함수 정의를 메모하고, 리렌더링 발생시 전달한 의존성 배열의 값이 변경될 때 반환 함수가 변경된다.

useMemo

값을 메모하고, 리렌더링이 발생시 전달한 의존성 배열의 값이 변경될 때 반환 값이 변경됨.
고비용 연산 결과값 재사용 및 컴포넌트 메모 api인 memo와 함께 사용해서 하위 컴포넌트 자체의 리렌더링을 막을 수 있다!

유의할 점
useCallback, useMemo와 같은 성능 최적화 도구 사용 시 trade-off를 고려할 필요가 있다.

비싼 연산 비용을 절약, 참조 동등성 보장 등의 이점이 있는 반면 함수 호출 비용, 의존성 배열 목록이 차지하는 메모리, 의존성 배열 목록 비교하는 비용, 코드 가독성 저하되는 등의 문제가 있다.

유의할 점에 따르면 최적화가 유의미할 때 최적화도구를 적용하는 것이 옳을 것이다. 객관적인 분석을 위한 방법이 있고 아래와 같은 방법을 활용해볼 수 있다.

  1. <Profiler /> : 리액트 컴포넌트 API
    • 컴포넌트가 렌더링에 소비한 시간과 리소스를 측정
    • id, phase, actualTime, baseTime, startTime, commitTime 정보를 얻을 수 있음
  2. React DevTools Profiler: 개발자 도구 익스텐션, 리액트팀 개발
    시각적으로 리렌더링되는 부분이 하이라이트됨
    전체 컴포넌트 트리의 각 컴포넌트 리렌더링 시간 추적
  3. Performance: 개발자 도구
    컴포넌트 생명주기 흐름 및 소요시간 추적

참고

Built-in React Hooks

0개의 댓글

관련 채용 정보