개발공부/REACT

Hooks의 개념과 useState, useEffect [처음 만난 리액트 #21]

sohee! 2023. 2. 3. 17:08

7강. Hooks

 

Hooks

- Hooks은 리액트가 처음 나왔을 때 부터 있던 개념은 아니고, 리액트 버전 16.8에서 새롭게 등장한 개념

- 최근에는 리액트로 개발을 할 때 대부분 Hooks을 사용

 

 

컴포넌트에는 두 가지 종류 - 함수 컴포넌트와 클래스 컴포넌트

State를 이용해 렌더링에 필요한 데이터를 관리 

 

 

● 클래스 컴포넌트에서는 생성자(constructor)에서 state를 정의하고 setState() 함수를 통해 state를 업데이트

- 클래스 컴포넌트는 state와 관련된 기능뿐만 아니라 컴포넌트의 생명주기함수들까지 명확하게 정의되어있기 때문에 잘 가져다 쓰기만 하면 됨

 

 함수 컴포넌트는 클래스 컴포넌트와 다르게 코드도 매우 간결하고, 별도로 state를 정의해서 사용하거나 컴포넌트의 생명주기에 맞춰 코드가 실행되도록 할 수 없었음

- 함수 컴포넌트에 이런 기능들을 지원하기 위해 나온 것이 Hooks

Hooks을 사용하면 함수 컴포넌트도 클래스 컴포넌트의 기능을 모두 동일하게 구현할 수 있음 

 


Hooks : 갈고리

프로그래밍에서 Hooks은 원래 존재하는 어떤 기능에 갈고리를 거는 것처럼 끼어들어가 같이 실행되는 것을 말함

ex) Web Hooks

 

리액트의 state와 생명주기기능에 갈고리를 걸어 원하는 시점에 정해진 함수(Hooks)를 실행되도록 만든 것 

 

Hooks의 이름은 모두 use로 시작 (각 기능을 사용하겠다는 의미)

- 개발자가 직접 커스텀 훅을 만들어서 사용할 수도 있는데, 커스텀 훅은 개발자 마음대로 이름을 지을 수 있지만 훅의 규칙에 따라 이름 앞에 use를 붙여서 훅이라는 것을 나타내주어야 함 

 


대표적인 Hooks

1. useState()

state를 사용하기 위한 Hook

함수 컴포넌트에서는 기본적으로 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를 하나씩 증가시키고, 현재 count를 보여주는 단순한 컴포넌트

 

위처럼 count를 함수의 변수로 선언해서 사용하게 되면, 버튼 클릭 시 count 값을 증가시킬 수는 있지만 재렌더링이 일어나지 않아 새로운 count값이 화면에 표시되지 않게 됨

-> state를 사용해서 값이 바뀔 때 마다 재렌더링되도록 해야하는데, 함수 컴포넌트에는 해당 기능이 따로 없으므로 useState()를 사용하여 state를 선언하고 업데이트 해야 함

 


useState() 사용법

const [변수명, set함수명] = useState(초기값);

 

● useState()를 호출할 때에는 파라미터로 선언할 state의 초기값이 들어감

(클래스 컴포넌트의 생성자에서 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 + )}>
                 클릭
             </button>
         </div>
    );
}

 

▲ useState를 사용하여 count값을 state로 관리하도록 만든 코드

 

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

state의 변수명, set 함수가 count, setCount

- 버튼이 눌렸을 때 setCount 함수를 호출해서 count를 1 증가시키고, count의 값이 변경되면 컴포넌트가 재렌더링되면서 화면에 새로운 count 값이 표시

 

(클래스 컴포넌트에서 setState 함수를 호출해서 state가 업데이트되고 이후 컴포넌트가 재렌더링되는 과정과 동일)

클래스 컴포넌트에서는 setState 함수 하나를 사용해서 모든 state값을 업데이트할 수 있었지만,

useState를 사용하는 방법에서는 변수 각각에 대해 set 함수가 따로 존재

 


2. useEffect()

Side effect를 수행하기 위한 Hook

 

Side effect?

= 부작용

 

● 컴퓨터 프로그래밍에서의 Side effect: 개발자가 의도치않은 코드가 실행되면서 버그가 나타나면 Side effect가 발생했다가고 함

● 리액트에서의 Side effect : 효과, 영향 

- 서버에서 데이터를 받아오거나 수동으로 DOM을 변경하는 등의 작업을 의미 

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

  (렌더링이 끝난 이후에 실행되어야 하는 작업들)

 

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

- 클래스 컴포넌트에서 제공하는 생명주기함수인 componentDidMount(), componentDidUpdate(), componentWillUnmount()와 동일한 기능을 하나로 통합해서 제공

 


useEffect() 사용법

useEffect(이펙트 함수, 의존성 배열);

 

첫 번째 파라미터로는 이펙트 함수가, 두 번째 파라미터로는 의존성 배열이 들어감

 

의존성 배열: effect가 의존하고 있는  배열

배열 안에 있는 변수 중에 하나라도 값이 변경되었을 때, 이펙트 함수가 실행

 

기본적으로 이펙트함수는 처음 함수가 렌더링 된 이후와 업데이트로 인한 재렌더링 이후에 실행

 

 

1) Effect function이 mount, unmount 시에 단 한번씩만 실행되게 하고 싶다면, 의존성배열에 빈 배열을 넣으면 됨

 

useEffect(이펙트 함수, []);

 

해당 이펙트가 props나 state에 있는 어떤 값에도 의존하지 않는 것이 되므로 여러 번 실행되지 않음

 

 

2) 의존성 배열을 생략하면 컴포넌트가 업데이트될 때 마다 호출됨

 

useEffect(이펙트 함수);

 


<useEffect()를 사용한 예제 코드>

 

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

function Counter(props) {
    const [count, setCount] = useState(0);
    
    // componentDidMount, componentDidUpdate와 비슷하게 작동합니다.
    useEffect(() => {
        // 브라우저 API를 사용해서 document의 title을 업데이트 합니다.
        document.title = `You clicked ${count} times`;
    });
    
    return (
        <div>
            <p>총 {count}번 클릭했습니다</p>
            <button onClick={() => setCount(count + 1)}>
                클릭
            </button>
        </div>
    );
}

 

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

 

useEffect() 안의 이펙트함수에서는 브라우저에서 제공하는 API를 사용해서 document의 title을 업데이트

- document의 title: 브라우저에서 페이지를 열었을 때 창에 표시되는 문자열 (크롬 브라우저의 경우 탭에 나오는 제목)

 

 

위 코드처럼 의존성배열 없이 useEffect()를 사용하면,

리액트는 DOM이 변경된 이후에 해당 이펙트함수를 실행하라는 의미로 받아들임

-> 기본적으로 컴포넌트가 처음 렌더링 될 때를 포함해서 매 번 렌더링 될 때 마다 이펙트가 실행

 

이펙트함수가 처음 컴포넌트가 mount되었을 때 실행되고, 이후 컴포넌트가 update될 때 마다 실행

:결과적으로 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.unsubscribeUserStatus(props.user.id, handleStatusChange);
        };
    });
    
    if(isOnline === null) {
        return '대기 중...';
    }
    return isOnline ? '온라인' : '오프라인';
}

 

useEffect에서 먼저 serverAPI를 사용하여 사용자의 상태를 구독하고 있음

이후 함수를 하나 리턴하는데, 해당 함수 안에는 구독을 해지하는 API를 호출하도록 되어있음

 

 

useEffect에서 리턴하는 함수는 컴포넌트가 Mount를 해제(unmount)될 때 호출됨

-> 결과적으로 useEffect의 리턴함수의 역할은 componentWillUnmount() 함수가 하는 역할과 동일

 


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

<두 개의 useEffect hook을 사용하는 예제 코드>

 

function UserStatusWithCounter(props) {
    const [count, setCount] = useState(0);
    useEffect(() => {
        document.title = `총 ${count}번 클릭했습니다.`;
    });
    
    const [isOnline, setIsOnline] = useState(null);
    useEffect(() => {
        ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
        return () => {
            ServerAPI.unscribeUserStatus(props.user.id, handleStatusChange);
        };
    });
    
    function handleStatusChange(status) {
        setIsOnline(status.isOnline);
    }
    
    // ...
}

 

▲ useState 훅과 useEffect 훅을 각각 두 개씩 사용하고 있음

 


useEffect hook의 사용법 정리

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