React Hook 개념 정리 (State, Effect 위주로)

YUKI KIM·2021년 12월 27일

React에서 Hook이란, 함수형 컴포넌트에서 사용되는, state와 관련된 기술들을 모아서 일컫는 말이다. 대표적으로 useState()useEffect()가 있는데 리액트 공식문서 HOOK를 읽으면서 정리해보았다.

Hook은 함수 컴포넌트에서 React state와 생명주기 기능(lifecycle features)을 “연동(hook into)“할 수 있게 해주는 함수입니다. Hook은 class 안에서는 동작하지 않습니다. 대신 class 없이 React를 사용할 수 있게 해주는 것입니다.



Hook 개요

Hook은 React 16.8에 새로 추가된 기능이다. Hook은 class를 작성하지 않고도 state와 다른 React의 기능들을 사용할 수 있게 해준다.

📌 State Hook

import React, { useState } from 'react';

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

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

위는 버튼을 클릭하면 값이 증가하는 간단한 카운터 예시다. 여기서 useState()가 바로 Hook이다. 이는 현재의 state 값과 이 값을 업데이트하는 함수를 쌍으로 제공한다. useState()의 인자는 초기 state 값이다.

⚡️ Effect Hook

종종 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작해야 할 때가 있다. 우리는 이러한 동작을 effects 라고 한다. 이는 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이다.

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

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

  useEffect(() => {
    // 브라우저 API를 이용해 문서의 타이틀을 업데이트
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

위는 리액트가 DOM을 업데이트한 뒤에 문서의 타이틀을 바꾸는 컴포넌트이다. useEffect()를 사용하면, 리액트는 DOM을 렌더링할 때마다 effects 함수를 실행한다. 그리고, effects는 컴포넌트 안에 선언되어 있으므로 props와 state에 접근할 수 있다.

위 예제는 cleanup이 필요없는 작업이다. 그러나 어떤 상태를 subscribe하고자 하는 경우에는 cleanup이 필요하다. 안 그러면 memory leak이 생기기 때문이다.

cleanup이란, useEffect()에서 parameter로 넣은 함수의 return 함수이다. 컴포넌트의 unmount 이전 / update 직전에 어떠한 작업을 수행하고 싶다면 cleanup 함수를 반환해줘야 한다.

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

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

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

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

위는 친구의 접속 상태를 구독하는 effect를 사용하고, 구독을 해지해서 해제하는 예제이다. 이 예시에서 컴포넌트가 unmount될 때 리액트는 ChatAPI에서 구독을 해지한다.

🔌 다른 내장 Hook

보편적이지는 않지만, 유용한 내장 Hook이 몇 가지 더 있다. 바로 useContext()useReducer()이다.

function Example() {
  const locale = useContext(LocaleContext);
  const theme = useContext(ThemeContext);
  // ...
}

useContext()는 컴포넌트를 중첩하지 않고 리액트 context를 구독할 수 있게 해준다.

function Todos() {
  const [todos, dispatch] = useReducer(todosReducer);
  // ...

useReducer()는 복잡한 컴포넌트들의 state를 reducer로 관리할 수 있게 해준다.


Using the State Hook

state 변수 선언하기

 import React, { useState } from 'react';
 
 function Example() {
    const [count, setCount] = useState(0);

    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
            Click me
            </button>
        </div>
    );
}

Hook을 사용하면 this 없이 state에 직접 접근하여 호출할 수 있다. useState는 state 변수, 해당 변수를 갱신할 수 있는 함수 이 두 가지 쌍을 반환한다. 이것이 바로 const [count, setCount] = useState()라고 쓰는 이유다.


Using the Effect Hook

useEffect()를 이용하면 리액트에게 컴포넌트 렌더링 이후 어떤 일을 할지 알려줄 수 있다. 리액트는 넘겨받은 함수를 기억했다가 (이 함수가 effect) DOM 업데이트 이후 불러낸다. effect를 통해 문서 타이틀을 지정하거나, 데이터를 가져오거나 API를 불러낼 수 있다.

  • Mount: DOM 객체가 생성되고 브라우저에 나타나는 것을 의미
  • Update: 컴포넌트가 업데이트(props 또는 state 변경, 부모 컴포넌트가 리렌더링) 될 때의 과정
  • Unmount: 컴포넌트가 DOM에서 제거되는 것을 의미

리액트의 calss 생명주기 메서드로 설명하자면, useEffect()componentDidMountcomponentDidUpdate, componentWillUnmount가 합쳐진 것으로 생각해도 좋다.

cleanup을 이용하지 않는 Effects

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

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

앞 절에서 언급했던 예제를 자세히 짚고 넘어가겠다. 위 코드에서 useEffect() 파라미터에 함수를 전달하고 있는데 이 함수가 바로 effect이다. 컴포넌트를 렌더링할 때 리액트는 이 effect를 기억하였다가 DOM을 업데이트한 이후 실행한다. 이는 매 렌더링 시 똑같이 적용된다.

cleanup을 이용하는 Effects

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

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
        ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
});

이것도 앞 절에서 언급했지만 이번 절에서 자세히 말하고자 한다. 리액트는 컴포넌트의 마운트가 해지될 때 cleanup을 실행한다. 하지만 effect는 매 렌더링마다 실행된다. 따라서 리액트가 다음 차례의 effect를 실행하기 전에 이전 렌더링에서 파생된 effect를 정리하는 것이다.

앞절에서 컴포넌트의 unmount 이전 / update 직전에 어떠한 작업을 수행하고 싶을 때 반환하는 것이 cleanup 함수라고 했다.

  • unmount 이전: useEffect(func, [])
  • 특정값 update 직전: useEffect(func, [특정값])

그리고 cleanup 함수를 사용하게되면, 작동 순서는 리렌더링 -> 이전 effect cleanup -> effect 로 실행이 된다.


레퍼런스

profile
유키링と 욘데 쿠다사이

0개의 댓글