React 공부 (9) 훅

seon·2024년 2월 1일

Web

목록 보기
16/33
post-thumbnail
  • 출처 : 소플의 처음 만나 리액트
    이번 강의에서는 리액트의 '훅'에 대해서 배워보도록 하겠습니다. 훅은 리액트가 처음 나왔을때부터 있던 개념은 아니고 리액트 버전 16.8 에서 새롭게 등장한 개념입니다.
    최근에는 리액트로 개발할 때 대부분 훅을 사용하기 때문에 훅에 대해 잘 이해하는 것이 중요합니다. 그럼 시작해볼까요?

# Hooks

먼저 훅이란 무엇인지에 대해서 배워보도록 하겠습니다. 앞에서 리액트 컴포넌트에 대해 배울 때 컴포넌트에는 두 가지 종류가 있다고 했었습니다.

하나는 클래스 컴포넌트이고 다른 하나는 함수 컴포넌트였죠. 또한 컴포넌트에는 state 라는 중요한 개념이 등장합니다. 이 state 를 이용해서 렌더링에 필요한 데이터를 관리하게 되죠.
클래스 컴포넌트에서는 생성자 constructor 에서 state를 정하고 setState() 함수를 통해 state를 업데이트합니다. 이처럼 클래스 컴포넌트는 state와 관련된 기능 뿐만 아니라 컴포넌트의 생명주기 함수들까지 모두 명확하게 정의되어 있기 때문에 잘 가져다 쓰기만 하면 됩니다.
하지만 기존 함수 컴포넌트는 클래스 컴포넌트와는 다르게 코드도 굉장히 간결하고 별도로 state 를 정의해서 사용하거나 컴포넌트의 생명주기에 맞춰 어떤 코드가 실행되도록 할 수 없었습니다. 따라서 함수 컴포넌트의 이런 기능을 지원하기 위해서 나온 것이 바로 훅입니다. 훅을 사용하면 함수 컴포넌트도 클래스 컴포넌트의 기능을 모두 동일하게 구현할 수 있게 되는 것이죠. 훅이라는 영어 단어는 갈고리라는 뜻을 갖고 있는데

갈고리

보통 프로그래밍에서는 원래 존재하는 어떤 기능에 마치 갈고리를 거는 것처럼 끼어 들어가 같이 수행되는 것을 의미합니다. 비슷하게 자주 사용되는 용어로는 'web hook' 이라는 것이 있죠. 리액트의 훅도 마찬가지로 리액트의 state 와 생명주기 기능의 갈고리를 걸어 원하는 시점에 정해진 함수를 실행되도록 만든 것입니다. 그리고 이때 실행되는 함수를 훅이라고 부르기로 정한 것이죠. 이러한 훅의 이름은 모두 use 로 시작합니다. 훅이 수행하는 기능에 따라서 이름을 짓게 되었는데 각 기능을 사용하겠다는 의미로 use를 앞에 붙여 씁니다. 개발자가 직접 커스텀 hook을 만들어서 사용할 수도 있는데 커스텀 훅은 개발자 마음대로 이름을 지을 수 있지만 뒤에서 배울 훅의 규칙에 따라 이름 앞에 use 를 붙여서 훅이라는 것을 나타내 주어야 합니다.
지금부터 대표적인 훅들에 대해서 하나씩 알아보도록 하겠습니다.



# useState()

먼저 가장 대표적이고 많이 사용되는 훅인 useState() 훅입니다.

useState() : state를 사용하기 위한 Hook

useState() 는 이름에서 알 수 있듯이 state를 사용하기 위한 훅입니다. 함수 컴포넌트에서는 기본적으로 state 라는 것을 제공하지 않기 때문에 클래스 컴포넌트처럼 state 를 사용하고 싶으면 useState() 훅을 사용해야 합니다. 간단한 예제 코드를 한번 보겠습니다.

import React, { useState } from "react";

function Counter(props) {
  var count = 0;
  
  return {
    <div>
    	<p>{count}번 클릭했습니다.</p>
		<button onClick={() => count++}>
    		클릭
  		</button>
	</div>
  );
}

위의 코드에는 Counter 라는 함수 컴포넌트가 등장합니다. Counter 컴포넌트는 버튼을 클릭하면 count 를 하나씩 증가시키고 현재 counter 를 보여주는 간단한 컴포넌트입니다. 그런데 만약 이처럼 count 를 함수 변수로 선언해서 사용하게 되면 버튼 클릭 시 count 값을 증가시킬 수는 있지만 재렌더링이 일어나지 않아 새로운 count 값이 화면에 표시되지 않게 됩니다. 따라서 이런 경우에는 state 를 사용해서 값이 바뀔 때마다 재렌더링되도록 해야 하는데 함수 컴포넌트에는 해당 기능이 따로 없기 때문에 useState() 를 사용하여 state를 선언하고 업데이트해야 합니다. useState() 훅은 다음과 같이 사용합니다. useState() 를 호출할 때에는 파라미터로 선언할 state의 초기값이 들어갑니다. useState(초기값)
클래스 컴포넌트의 생성자에서 state를 선언할 때 초기값을 넣어주는 것과 동일한 것이라고 보면 됩니다. 이렇게 초기값을 넣어 useState()를 호출하면 리턴값으로 배열이 나옵니다.
리턴된 배열에는 두가지 항목이 들어있는데 첫번째 항목은 state로 선언된 변수이고 두번째 항목은 해당 state의 set함수입니다. 실제로 useState() 를 사용하는 코드를 보겠습니다.

import React, { useState } from "react";

function Counter(props) {
  const [count, setCount] = useState(0);
  
  return (
    <div>
    	<p>{count}번 클릭했습니다.</p>
		<button onClick={() => setCount(count + 1)}>
          클릭
		</button>
	</div>
  );
}

이 코드는 useState() 를 사용하여 count 값을 state 로 관리하도록 만든 것입니다. 이 코드에서 state 변수명과 함수가 각각 count, setCount 로 되어 있는 것을 볼 수 있습니다. 버튼이 눌렸을 때 setCount 함수를 호출해서 count를 1 증가시킵니다. 그리고 count의 값이 변경되면 컴포넌트가 재렌더링되면서 화면에 새로운 count 값이 표시됩니다. 이 과정은 클래스 컴포넌트에서, setState 함수를 호출해서 state 가 업데이트되고 이후 컴포넌트가 재렌더링되는 과정과 동일하다고 보면 됩니다.
다만 클래스 컴포넌트에서는 setState 함수 하나를 사용해서 모든 state 값을 업데이트할 수 있었지만, useState()를 사용하는 방법에서는 변수 각각에 대해 set함수가 따로 존재한다는 것을 기억하시기 바랍니다.



# useEffect()

useState 와 같이 가장 많이 사용되는 훅으로 useEffect가 있습니다.

useEffect() : Side effect를 수행하기 위한 Hook

useEffect() 는 side effect 를 수행하기 위한 훅입니다. 그렇다면 side effect가 무엇인지부터 알아야겠죠?

Side effect = 부작용

Side effect는 사전적으로 부작용이라는 뜻을 갖고 있습니다. 원래 단어의 의미 자체가 부정적인 의미를 가지고 있는데 컴퓨터 프로그래밍에서도 부정적인 의미로 사용되곤 합니다.
개발자가 의도치 않은 코드가 실행되면서 버그가 나타나면 side effect 가 발생했다고 말합니다. 하지만 리액트에서 side effect는 부정적인 의미는 아닙니다.

Side effect = 효과, 영향

리액트에서 말하는 side effect는, 그냥 효과 혹은 영향을 뜻하는 effect의 의미에 가깝습니다. 예를 들면 서버에서 데이터를 받아오거나 수동으로 도움을 변경하는 등의 작업을 의미합니다. 이런 작업을 effect라고 부르는 이유는, 이 작업들이

다른 컴포넌트에 영향을 미칠 수 있으며, 렌더링 중에는 작업이 완료될 수 없기 때문

입니다. 렌더링이 끝난 이후에 실행되어야하는 작업들이죠. 그래서 이러한 작업들이 side 로 실행된다는 의미에서 side effect 라고 불리며, useEffect() 는

리액트의 함수 컴포넌트에서 Side effect를 실행할 수 있게 해주는 Hook

입니다. useEffect는 클래스 컴포넌트에서 제공하는 생명주기 함수인 componentDidMountDidUpdate 그리고 componenetWillUnmount 와 동일한 기능을 하나로 통합해서 제공합니다. 그래서 useEfeect() 훅만으로 이러한 생명주기 함수와 동일한 기능을 수행할 수 있습니다.

useEffect() 는 다음과 같이 사용합니다.

  • 첫 번째 파라미터로는 이팩트 함수가 들어가고 두 번째 파라미터로는 의존성 배열이 들어가고,
  • 두 번째 파라미터로는 의존성 배열이 들어갑니다.
    의존성 배열은 말 그대로 이 이펙트가 의존하고 있는 배열인데, 배열 안에 있는 변수 중에 하나라도 값이 변경되었을 때 이펙트 함수가 실행됩니다. 기본적으로 이펙트 함수는 처음 컴포넌트가 렌더링된 이후와, 업데이트로 인한 재렌더링 이후에 실행됩니다.

  • 만약 이펙트 함수가 mount와 unmount 시에 단 한 번씩만 실행되게 하고 싶으면 이렇게 의존성 배열에 빈 배열[]을 넣으면 됩니다.
    이렇게 하면 해당 이펙트가 props나 state에 있는 어떤 값에도 의존하지 않는 것이 되므로 여러 번 실행되지 않습니다.

  • 그리고 의존성 배열은 생략할 수도 있는데, 생략하게 되면 컴포넌트가 업데이트될 때마다 호출됩니다.

useEffect()를 사용한 예제 코드를 한번 보겠습니다.

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

function Counter(props) {
  const [count, setCount] = useState(0);
  
  // componenetDidMount, componenetDidUpdate와 비슷하게 작동합니다.
  useEffect(() => {
    //브라우저 API를 사용해서 document의 title을 업데이트 합니다.
    document.title = 'You clicke ${count} times':
  });
  
  return (
    <div>
    	<p>{count}번 클릭했습니다.</p>
		<button onClick={() => setCount(count + 1)}>
          클릭
		</button>
	</div>
  );
}
  • 이 코드는 앞에서 useState를 배울 때 살펴본 코드와 거의 동일하며, 추가로 useEffect 훅을 사용하고 있습니다.

  • useEffect()를 사용해서 클래스 컴포넌트에서 제공하는 componentDidMount, componentDidUpdate와 같은 생명주기 함수의 기능을 동일하게 수행하도록 만들었습니다.

  • useEffect() 안에 있는 이펙트 함수에서는, 브라우저에서 제공하는 API를 사용해서 document의 title을 업데이트 합니다. document의 title은 우리가 브라우저에서 페이지를 열었을 때 창에 표시되는 문자열입니다. 크롬 브라우저의 경우 탭의 나오는 제목이라고 보면 됩니다.

  • 이 코드처럼 의존성 배열 없이 useEffect()를 사용하면 리액트는 DOM이 변경된 이후에 해당 이펙트 함수를 실행하라는 의미로 받아들입니다. 그래서 기본적으로 컴포넌트가 처음 렌더링될 때를 포함해서 매번 렌더링될 때마다 이펙트가 실행된다고 보면 됩니다. 이 코드의 경우 이펙트 함수가 처음 컴포넌트가 mount되었을 때 실행되고 이 컴포넌트가 업데이트될 때마다 실행됩니다. 결과적으로 componentDidMount, componentDidUpdate와 동일한 역할을 하게 되는 것이죠.

  • 또한 이펙트는 함수 컴포넌트 안에서 선언되기 때문에 해당 컴포넌트의 props와 state에 접근할 수도 있습니다. 이 코드에서는 count라는 state에 접근하여 해당 값이 포함된 문자열을 생성해서 사용하는 것을 볼 수 있습니다.


그렇다면 componentWillUnmount와 동일한 기능은 useEffect()로 어떻게 구현할 수 있을까요? 이 예제 코드를 한번 보겠습니다.

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

function UserStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  
  useEffect(() => {
    ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
    return () => { //
      ServerAPI.unsubscibeUserStatus(props.user.id, handleStatusChange);
    }; //컴포넌트가 unmount 될 때 호출됨!
  });
  
  if (isOnline == null) {
    return '대기 중...';
  }
  return isOnline ? '온라인' : '오프라인';
}
  • 이 코드는 useEffect에서 먼저 ServerAPI를 사용하여 사용자의 상태를 구독하고 있습니다. 이후 함수를 하나 리턴하는데, 해당 함수 안에는 구독을 해제하는 API를 호출하도록 되어 있습니다. useEffect()에서 리턴하는 함수는 컴포넌트가 mount 해제, 즉 unmount 될 때 호출됩니다.
  • 결과적으로 useEffect()의 return 함수의 역할은 componentWillUnmount 함수가 하는 역할과 동일합니다.

또한 useEffect() 훅은 하나의 컴포넌트에 여러 개를 사용할 수 있습니다.

function UserStatusWithCounter(props) {
  const [count, setCount] = useState(0);
  useEffect(()=>{
    document.title = '총 ${count}번 클릭했습니다.';
  });
  
  const [isOnline, setIsOnline] = useState(null);
  useEffect(()=>{
    ServerAPI.subscibeUserStatus(props.user.id, handleStatusChange);
    return () => {
      ServerAPI.unsubscribeStatus(props.user.id, handleStatusChange);
    };
  });
  
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }
  
  // ...
  • 이 코드는 두 개의 useEffect() 훅을 사용하는 예제 코드입니다.
  • useState() 훅과 useEffect() 훅을 각각 2개씩 사용하는 것을 볼 수 있습니다.

지금까지 배운 useEffect() 훅의 사용법을 다시 자세하게 정리해보겠습니다. - 기본적으로 이펙트 함수와 의존성 배열이 들어가고,

useEffect(() => {
  // 컴포넌트가 마운트 된 이후,
  // 의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
  // 의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트시에 단 한 번씩만 실행됨
  // 의존성 배열 생략 시 컴포넌트 업데이트 시마다 실행됨
  ...
  
  return () => {
    // 컴포넌트가 마운트 해제되기 전에 실행됨
    ...
  }
}, [의존성 변수1, 의존성 변수2, ...]);

📍정리

  • 훅 : 함수 컴포넌트에서 클래스 컴포넌트의 기능(state, Lifecycle)을 동일하게 구현할 수 있게 하는 '함수 갈고리'
  • useState() : state 사용을 위한 훅
const [변수명, set함수명] = useState(초기값);

※단, 클래스 컴포넌트에서는 set함수 하나를 사용해서 모든 state 값을 업데이트할 수 있었지만, useState() 이용시 변수 각각에 대해 set함수가 따로 존재함

  • useEffect() : side effect(렌더링이 끝난 이후 side로 실행되는 효과)를 위한 훅, 생명 주기 함수와 동일한 기능.
useEffect(이펙트 함수, 의존성 배열);
useEffect(이펙트 함수, []); //이펙트 함수가 mount, unmount 시 단 한번씩만 실행
useEffect(이펙트 함수); //컴포넌트가 업데이트될 때마다 호출됨

이 훅은 하나의 컴포넌트에 여러 개를 사용할 수 있음

  • useMemo() : 다음 포스트에서

정리한 부분을 읽어보면서 복습하시기 바랍니다.

profile
🌻

0개의 댓글