본문 바로가기

언어/JavaScript

[React] Hook 개요

반응형

Hook은 하위 호환성을 가지고 있습니다.

이 문서는 React에 경험이 있는 사용자를 대상으로 Hook에 대해 간략히 소개합니다.

 

State Hook

버튼을 클릭하면 값이 증가하는 간단한 카운터 예시가 여기 있습니다.

 

import React, { useState } from 'react';

function Example() {
  // "count"라는 새 상태 변수를 선언합니다
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

여기서 useState가 바로 Hook입니다.

Hook을 호출해 함수 컴포넌트(function component) 안에 state를 추가했습니다. 이 state는 컴포넌트가 다시 렌더링되어도 그대로 유지될 것입니다. useState는 현재의 state 값과 이 값을 업데이트하는 함수를 쌍으로 제공합니다. 우리는 이 함수를 이벤트 핸들러나 다른 곳에서 호출할 수 있습니다. 이것은 class의 this.setState와 거의 유사하지만, 이전 state와 새로운 state를 합치지 않는다는 차이점이 있습니다.

 

useState는 인자로 초기 state 값을 하나 받습니다. 카운터는 0부터 시작하기 때문에 위 예시에서는 초기값으로 0을 넣어준 것입니다. this.state와는 달리 setState Hook의 state는 객체일 필요가 없습니다. 물론 원한다면 그렇게도 가능합니다. 이 초기값은 첫 번째 렌더링에만 딱 한번 사용합니다.

 

여러 state 변수 선언하기

 

하나의 컴포넌트 내에서 State Hook을 여러 개 사용할 수도 있습니다.

function ExampleWithManyStates() {
  // 상태 변수를 여거 개 선언했습니다!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

배열 구조 분해 문법은 useState로 호출된 state 변수들을 다른 변수명으로 할당할 수 있게 해줍니다.

이 변수명은 useState API와 관련이 없습니다.

대신에 React는 매번 렌더링할 때 useState가 사용된 순서대로 실행할 것입니다. 

 

근데 훅이 뭔가요?

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

 

React는 useState같은 내장 Hook을 몇 가지 제공합니다. 컴포넌트 간에 상태 관련 로직을 재사용하기 위해 Hook을 직접 만드는 것도 가능합니다. 일단 내장 Hook을 먼저 보겠습니다.

 

Effect Hook

React 컴포넌트 안에서 데이터를 가져오거나 구독하고, DOM을 직접 조작하는 작업을 이전에도 종종 해보셨을 것입니다. 우리는 이런 모든 동작을 "side effects" (또는 짧게 "effects")라고 합니다. 왜냐하면 이것은 다른 컴포넌트에 영향을 줄 수도 있고, 렌더링 과정에서는 구현할 수 없는 작업이기 때문입니다.

 

Effect Hook, 즉 useEffect는 함수 컴포넌트 내에서 이런 side effects를 수행할 수 있게 해줍니다. React class의 componentDidMount나 comonentDidUpdate, componenntWillUnmount와 같은 목적으로 제공되지만, 하나의 API로 통합된 것입니다.

 

예를 들어, 이 예시는 React가 DOM을 업데이트한 뒤에 문서의 타이틀을 바꾸는 컴포넌트입니다.

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

function Examxple() {
  const [count, setCount] = useState(0);
  
  // componentDidMount, componentDidUpdate와 비슷합니다
  useEffect(() => {
    // 브라우저 API를 이용해 문서의 타이틀을 업데이트합니다
    document.title = 'You clicked ${count} times';
  });
  
  return (
    <div>
      <p>You clicked {count} titmes</p>
      <button conClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect를 사용하면, React는 DOM을 바꾼 뒤에 "effect" 함수를 실행할 것입니다. Effects는 컴포넌트 안에 선언되어있기 때문에 props와 state에 접근할 수 있습니다. 기본적으로는 React는 매 렌더링 이후에 effects를 실행합니다. 첫 번째 렌더링도 포함해서요.

 

Effect를 "해제"할 필요가 있다면, 해제하는 함수를 반환해주면 됩니다. 이는 선택적입니다. 예를 들어, 이 컴포넌트는 친구의 접속 상태를 구족하는 effect를 사용했고, 구독을 해지함으로써 해제해줍니다.

 

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

function FriendStatus(props) {
  const [isOnline, setIsOline] = useState(null);
  
  function handleStatusChange(Status) {
    setIsOnline(status.isOnline);
  }
  
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      chatAPI.unscribeFromFriendStatus(props.friend.id, handleeStatusChange);
    };
  });
  
  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

 

이 예시에서 컴포넌트가 unmount될 때 React는 ChatAPI에서 구독을 해지할 것입니다. 또한 재 렌더링이 일어나 effect를 재실행하기 전에도 마찬가지로 구독을 해지합니다. (만약 원한다면 props.friend.id가 바뀌지 않았을 때 재구독을 건너뛰도록 설정할 수 있습니다.)

 

useState와 마찬가지로 컴포넌트 내에서 여러 개의 effect를 사용할 수 있습니다.

 

function FriendStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
}
// ...

Hook을 사용하면 구독을 추가하고 제거하는 로직과 같이 서로 관련있는 코드들을 한군데에 모아서 작성할 수 있습니다. 반면 class 컴포넌트에서는 생명주기 메서드 각각에 쪼개서 넣어야만 했습니다.

 

Hook 사용 규칙

Hook은 그냥 JavaScript 함수이지만, 두 가지 규칙을 준수해야 합니다.

1. 최상위 레벨에서만 Hook을 호출해야 합니다. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하지 마세요.

2. React 함수 컴포넌트 내에서만 Hook을 호출해야합니다. 일반 JavaScript 함수에서는 Hook을 호출해서는 안됩니다. (Hook을 호출할 수 있는 곳이 딱 한 군데 더 있습니다. 바로 직접 작성한 custom Hook 내입니다. 이것에 대해서는 나중에)

 

이 규칙들을 강제하기 위해서 linter plugin을 제공하고 있습니다. 이 규칙들이 제약이 심하고 혼란스럽다고 처음에는 느낄 수 있습니다. 하지만 이것은 Hook이 제대로 동작하기 위해서는 필수적인 조건입니다.

 

나만의 Hook 만들기와 useContext같은 다른 내장 Hook은 다음에..

반응형