React Hook 종류와 사용법 알아보기

jellyjw·2023년 8월 13일
27
post-thumbnail

개요

리액트에 다양한 Hook이 존재하지만 매번 사용하는 Hook만 사용하는 것 같아서 React Hook의 종류를 알아보고 상황에 따라 어떤 Hook을 효율적으로 사용할 수 있을지, 사용시 주의할 점에 대해서도 알아보려 한다.


✅ React Hooks 란?

Hooks는 React 16.8버전부터 도입된 개념으로, 클래스 컴포넌트에서만 가능했던 상태 관리 및 생명 주기 메서드를 함수형 컴포넌트에서도 사용할 수 있도록 도와주는 기능이다.

Hooks

  • useState : 가장 자주 사용되는 useState는 함수형 컴포넌트에서 상태(state) 를 관리할 수 있게 해주는 훅으로, 상태값을 변경하고 업데이트 할 수 있다.
  • useEffect : 컴포넌트 렌더링 이후 특정 작업(data fetching, DOM 조작 등) 을 수행할 수 있게 해준다.
  • useContext : 리액트의 Context API 를 사용하여, 컴포넌트 트리 안에서 전역적인 상태 공유 를 돕는다.
  • useCallback : 함수 를 메모이제이션하여 성능 최적화를 돕는다. 부모 컴포넌트가 리렌더링되어도 함수를 새로 생성하지 않고 재사용해야 할 경우 사용된다.
  • useMemo : 을 메모이제이션하여 성능 최적화를 돕는다. 계산 비용이 큰 연산을 수행하거나, 동일한 값을 여러번 계산하지 않도록 할 때 사용된다.
  • useRef : 컴포넌트에서 DOM 요소를 선택하거나 참조할 때 사용된다. ref 객체를 생성하여 DOM 요소에 접근할 수 있다.
  • useLayoutEffect : useEffect 와 유사하게 작동하지만, 렌더링 된 후 동기적으로 실행된다. 보통 DOM 조작과 관련된 작업에 사용된다.

✅ Hook 사용 규칙

React에서 Hook을 사용하기 위해서는 두가지 규칙을 지켜야 한다.

1) 최상위 레벨에서만 Hook을 호출할 것

Hook은 항상 함수형 컴포넌트의 최상위 레벨에서 호출되어야 한다.

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    ...
  );
}

export default Counter;

왜 최상위 레벨에서만 호출되어야 할까?
리액트에서는 컴포넌트가 렌더링 될때마다 동일한 순서로 Hook을 호출하게 되기 때문, 즉 React가 Hook 이 호출되는 순서에 의존하기 때문이다.

function User() {
  const [name, setName] = useState('Jelly');
  
   if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
}

만약 위 코드처럼 조건문이나 반복문 내에서 Hook을 호출하게 될 경우 렌더링마다 Hook의 호출 순서가 달라져 컴포넌트의 상태관리 및 렌더링이 예측 불가하게 동작할 수 있기 때문에, 반드시 최상위 레벨에서 호출함으로써 컴포넌트의 상태와 렌더링을 안정적으로 관리할 수 있다.

2) React 함수 컴포넌트 내에서 Hook을 호출할 것

Hooks는 React의 함수 컴포넌트 내부에서만 사용되어야 한다. Hook이 당연하게도 React 내부에서만 동작하도록 설계되었기 때문이다.

컴포넌트 외부에서 호출시 동작하지 않는다.

const age = useState(0); // 동작 X
function Component() {
  // ...
}

✅ Hook 자세히 알아보기

1) useState

useState는 함수형 컴포넌트에서 상태를 관리하는 데 사용된다. 컴포넌트의 상태값을 추가하고, 값을 변경할 수 있다.

useState 는 배열을 반환하며, 첫번째 요소는 상태값, 두번째 요소는 해당 상태값을 업데이트하는 함수이다. 구조분해할당 문법을 사용하여 주로 다음과 같이 사용한다.

const [state, setState] = useState('')

⚠️ useState는 비동기적으로 동작한다

React는 상태를 업데이트 하는 과정에서 setState 같은 상태관리 함수 호출이 여러번 호출될 경우 불필요한 렌더링을 줄이기 위해 state 를 즉시 변경하지 않고, 리액트 엔진에 의해 일괄적으로 처리하는데, 이를 배치(batch) 라고 한다.

배치(batch)

React의 batch는 여러 상태 업데이트를 하나로 묶어서 처리하는 메커니즘이다.
이벤트 핸들러 내에서의 상태 업데이트 또한 일괄 처리한다.
즉, 동일한 이벤트 핸들러 내에서 여러번의 상태 업데이트가 발생해도 한번의 렌더링으로 처리된다.

import React, { useState } from 'react';
function Counter() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  const handleIncrement = () => {
    // 여러 상태 업데이트가 발생하지만, 일괄 처리됨
    setCount1(count1 + 1);
    setCount2(count2 + 1);
  };
  return (
    <button onClick={handleIncrement}>버튼</button>
  );
}
export default Counter;

처음 이벤트 핸들러 내에서 여러개의 setState로 상태 업데이트를 한꺼번에 시킬 때, 내가 생각했던 순서대로 동작하지 않아 당황했던 적이 많았다.

이처럼 비동기적으로 동작하는 useState 를 동기적으로 사용하기 위해서는 주로 useEffect 훅을 이용해 dependency에 특정 state를 넣고 해당 state가 변경될때마다 원하는 동작을 실행시키는 방법이 가장 많이 사용된다.


2) useCallback

useCallback은 성능 최적화를 위해 함수를 메모이제이션하고 재사용할 수 있는 훅이다. 컴포넌트 리렌더링시 불필요한 함수 재생성을 방지하고, 렌더링 성능을 개선할 수 있다.

import React, { useState, useCallback } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  
  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]); // count가 변경될 때만 메모이제이션된 함수를 업데이트

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

export default Counter;

handleClick 함수는 count 값에 의존하고 있기 때문에, count가 변할때만 함수가 재생성되어 재사용된다. 주로 자주 변경되지 않는 이벤트 핸들러나 콜백함수를 메모이제이션해 최적화하는 데 유용하게 사용된다.

3) useMemo

useMemo는 계산 비용이 큰 연산을 수행하거나, 동일한 값을 여러번 계산하지 않도록 할 때 사용된다. 같은 연산을 반복하여 수행하지 않고, 이전에 계산한 결과를 재사용하여 불필요한 작업을 줄일 수 있다.

const memoizedValue = useMemo(() => getValue(a, b), [a, b]);

의존성 배열이 변경될때에만 새로운 값을 계산하고, 그 외의 경우 메모이제이션한 값을 사용한다.

useCallback 과 useMemo의 차이는?

useCallbackuseMemo 는 둘다 메모이제이션을 통해 성능 최적화를 도와주는 훅이지만 useCallback은 함수 를 재사용하고, useMemo는 을 재사용한다는 차이가 있다.

Memoization

메모이제이션은 이전에 계산한 결과를 저장하고, 동일한 입력이 주어질 경우 미리 계산한 값을 반환하여 불필요한 연산을 줄이는 최적화 기법이다. 계산 비용이 크거나, 반복적으로 수행되는 작업을 최적화하는 데 사용된다.

사용시 주의할 점

성능 최적화를 위해 사용하는 훅이지만, 메모이제이션을 위해 따로 메모리를 사용하기 때문에 과도한 사용은 피하는게 좋다. 말그대로 복잡하고 비용이 많이 드는 연산을 수행할 때 사용해야 한다.

만약 단순한 값을 계산하거나 간단한 변수를 할당하는 경우, 의존생 배열 안의 값이 자주 변하는 경우에 사용한다면 불필요한 메모이제이션을 유발해 오히려 성능이 악화될 수 있다.


4) useRef

useRef는 DOM 요소나 컴포넌트 내부에서 특정 값을 관리하고 유지할때 사용되는 훅이다.

가장 큰 특징은, useRef 를 사용하면 값이 변경되어도 컴포넌트가 리렌더링되지 않으며, 값의 변경이 발생해도 상태가 업데이트 되지 않는다는 점이다.

import React, { useRef } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  const handleClick = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleClick}>Focus Input</button>
    </div>
  );
}

export default FocusInput;

대표적으로 input 필드에 포커스를 주는 예제이다. inputRef 라는 객체를 생성하고, input ref에 할당하여 해당 요소를 참조하도록 했다.

useRef는 주로 이런 용도로 쓰인다.

  • DOM 요소에 직접 접근 하거나 조작해야 할 경우
  • 컴포넌트 리렌더링시에도 이전 값의 변경을 추적하거나 저장해야 할 경우

주의할 점

useRef 로 값을 변경해도 컴포넌트는 리렌더링 되지 않는다. 불필요한 리렌더링을 방지할 수 있지만, 리렌더링이 필요한 경우에는 useStateuseEffect 를 사용하여 리렌더링 시켜주어야 한다.

특히, 리액트에서 DOM을 직접 조작하는것은 주의가 필요하다. 리액트는 가상 DOM을 사용해서 변경사항을 업데이트 하는데, DOM을 직접 조작하는것 자체가 리액트 핵심 원칙과 어긋나기 때문이다.

가능하다면 props와 state를 활용하여 컴포넌트를 렌더링하고 업데이트하는 방식이 좋지만, 필요에 따라 동적 DOM 엘리먼트 조작이 필요하거나 특정 DOM 이벤트 핸들링이 필요할 경우 사용할 수 있다.


5) useEffect

useEffect는 컴포넌트 렌더링 이후 특정 작업을 수행할수 있게 해주는 훅이다.
컴포넌트가 마운트 될 때, 업데이트 될 때, 언마운트 될 때 호출이 가능하다.

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `${count}`;
  });

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

이 함수는 컴포넌트가 렌더링 될때마다 실행된다. 만약 특정 state가 변할때나, 컴포넌트 처음 렌더링 시 등 특정 상황에서만 사용하고 싶다면 두번째 인자로 의존성 배열(dependency array)을 넘겨 제어가 가능하다.

 useEffect(() => {
    document.title = `${count}`;
  }, [count]); // count 값이 변경될 때만 실행

주의할 점

여러개의 useEffect를 호출할 경우, 컴포넌트 렌더링시마다 모든 useEffect가 실행되기 때문에 순서를 명확하게 지정해주어야 한다.

또한, 비동기 작업 수행시 컴포넌트 언마운트 또는 업데이트 경우에 해당 작업 관리에 유의해야 한다. 이를 위해 clean-up 함수를 리턴하거나 의존성 배열을 활용할 수 있다.

clean-up 함수

 useEffect(() => {
    const timer = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    // clean-up 함수
    return () => {
      clearInterval(timer); // 타이머 해제
    };
  }, []); // 빈 의존성 배열: 마운트될 때에만 실행

useEffect 내부에서 리턴되는 함수를 의미하며 컴포넌트가 언마운트 될 때, 디펜던시 배열 내의 특정 값이 변경되기 전에 실행할 작업을 지정할 수 있다.


6) useLayoutEffect

useLayoutEffect는 컴포넌트의 렌더링이 화면에 실제로 그려진 후에 동기적으로 실행되는 효과를 만들기 위해 사용된다.

일반적으로 브라우저 레이아웃과 관련된 작업을 수행하거나 DOM 요소의 크기와 위치를 측정하는 등의 작업에 사용된다.

  useLayoutEffect(() => {
    function updateWidth() {
      setWidth(window.innerWidth);
    }

    updateWidth(); // 초기 렌더링 시에 크기를 업데이트
    window.addEventListener('resize', updateWidth); // 브라우저 크기 변경 시 크기 업데이트

    return () => {
      window.removeEventListener('resize', updateWidth); // 컴포넌트 언마운트 시 이벤트 리스너 해제
    };
  }, []);

useEffect와 useLayoutEffect의 차이는?

  • 타이밍 : 두 훅 모두 컴포넌트 렌더링 후에 실행된다. 하지만, useEffect비동기 적으로 실행되고 useLayoutEffect 는 브라우저가 레이아웃을 계산한 뒤 동기적으로 실행된다는 점에 차이가 있다.

useEffect

  • paint : 실제 스크린에 Layout을 표시하고 업데이트하는 과정
  • render : DOM Tree 를 구성하기 위해 각 엘리먼트의 스타일 속성을 계산하는 과정

useEffect는 컴포넌트가 render와 paint(실제 브라우저에 레이아웃을 그리는 과정) 과정이 완료된 후 비동기적으로 실행된다. 페인트 후에 동작하므로 만약 DOM을 조작하는 코드가 있을경우, 사용자가 깜빡임을 경험할 수 있다.

useLayoutEffect

useLayoutEffect는 컴포넌트가 render(DOM Tree를 구성하기 위해 각 엘리먼트의 스타일 속성을 계산하는 과정) 된 후 paint 과정을 거치기 전에 동기적으로 실행된다.

useLayoutEffect 의 내부 코드가 동기적으로 실행된 뒤 페인트 과정을 거치기 때문에, 복잡한 로직이 있을 경우 사용자가 레이아웃을 보기까지 시간이 오래 걸릴수 있어 대부분의 경우 useEffect 를 사용하여 처리하되, 대표적으로 화면 깜빡임을 개선해야 할 때 사용하는것이 권장된다.

React 컴포넌트 초기 렌더링과 리렌더링 과정

paint와 rendering의 타이밍을 이해하기 위해, 리액트에서 컴포넌트를 렌더링하는 과정을 찾아보았다.

단계초기 렌더링리렌더링
1. 함수 컴포넌트 호출컴포넌트 함수가 호출됨컴포넌트 함수가 재호출됨
2. 구현부 실행Props를 취득하고, Hook 실행, 내부 변수 및 함수 생성Props를 취득하고, Hook 재실행, 내부 변수 및 함수 재생성
3. return 실행렌더링 시작렌더링 시작
4. 렌더링 실행새로운 가상 DOM 생성 후 이전 가상 DOM과 비교하여 변경 사항 탐색, 실제 DOM에 반영할 부분 결정변경된 부분을 가상 DOM과 비교하여 변경 사항 탐색, 실제 DOM에 반영할 부분 결정
5. 커밋 단계변경된 부분을 실제 DOM에 반영변경된 부분을 실제 DOM에 반영
6. useLayoutEffect 실행동기적으로 실행됨동기적으로 실행됨 (state나 전역 상태값 변경 시 추가 실행 가능)
7. paint실제 DOM을 화면에 그림실제 DOM을 화면에 그림
8. useEffect 실행Mount 되어 브라우저에 그려진 후 비동기적으로 실행됨 (return이 있는 경우 실행됨)비동기적으로 실행됨 (return이 있는 경우 실행됨)

Reference

profile
남는건 기록뿐👩🏻‍💻

2개의 댓글

comment-user-thumbnail
2023년 8월 15일

정말 잘봤습니다.

답글 달기
comment-user-thumbnail
2023년 8월 21일

Thank you blog for giving me short and meaningful articles, helping me learn a lot of valuable information in a short time, I have a pretty good website Drift Hunters.

답글 달기