React - Hooks

milkbottle·2024년 1월 3일

React

목록 보기
8/33

Hooks

이전에서 함수형 컴포넌트와 클래스형 컴포넌트에 대한 차이를 공부하였다.

함수형 컴포넌트에서 상태관리와 라이프사이클에 도움을 주는 것이 Hooks이다.

이 Hooks의 종류는 다양하다.

  1. useState
  2. useEffect
  3. useMemo
  4. useRef
  5. 커스텀 훅

이외에도 useContext, useReducer등 다양하지만, 6개를 소개하겠다.

useState

이름에서도 알 수 있듯이 상태관리에 사용하는 Hook이다.

사용법

사용법은 다음과 같다.

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

버튼을 누른 횟수가 적혀있고 이를 클릭할 때 마다 숫자가 점점 올라가는 것을 구현하고 싶다면,

숫자가 계속 변하는 값이므로 상태에 저장해야한다.

import React, { useState } from 'react';

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

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

이런식으로 사용할 수 있다.

useEffect

라이프사이클과 연관된 Hook이다.

제일 중요한 3가지가 있다고 하였다. 바로 생과 사, 그리고 업데이트이다.

useEffect는 사용법이 좀 복잡하지만 하나의 함수를 통해 이 3가지를 표현이 가능하다.

사용법

useEffect(이펙트함수, [감지할 변수1, 감지할 변수2, ...]);

여기서 감지할 변수란 값이 변하면 사용자에게 콘솔로그를 찍어주거나, css 스타일을 변하게 하거나 등등을

하고 싶을 때 해당 변수들을 두 번째 인자 배열 안에 넣는다.

Mount, Unmount 시 실행(생과 사)

컴포넌트가 생성되었을 때, 그리고 사라질 때 두 생명주기 때 사용하는 방법이다.

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

두 번째 인자인 감지할 변수의 배열을 빈 배열로 넣는다.

왜냐하면 컴포넌트가 생성되고 사라지는 것은 변수와는 의존성이 전혀없기 때문이다.

그리고 이펙트 함수 안에서 컴포넌트의 생과 사에 대한 분기를 나눌 수 있다.

만약 컴포넌트가 생성되고 데이터를 API로 가져오는 상황이라고 하자.

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

function ExampleComponent() {
  const [data, setData] = useState([]);

  useEffect(() => {
    // 데이터 가져오기
    fetchData();
  }, []); // 두 번째 매개변수로 빈 배열을 전달하면 마운트 시에만 실행

  return (
    <div>
      {/* 렌더링에 사용할 UI */}
    </div>
  );
}

이렇게 하면 fetchData()라는 함수가 컴포넌트가 생성되었을 때 바로 실행된다.

엥? 그러면 Unmount 시에 실행하는 것은 어떻게 구현하는거야?

바로 이펙트 함수 내부에 return 으로 실행할 수 있다.

useEffect(() => {
    // 컴포넌트가 처음 마운트될 때 실행되는 부분
    const fetchData = async () => {
      try {
        const response = await fetch('https://api.example.com/data');
        const result = await response.json();
        setData(result);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };

    fetchData();

    // 컴포넌트가 언마운트될 때 정리(clean-up) 함수
    return () => {
      console.log('Component will unmount. Clean-up here.');
      // 여기에서 언마운트 시에 수행할 작업을 추가할 수 있습니다.
    };
  }, []); // 빈 배열을 전달하면 컴포넌트가 처음 마운트될 때만 실행

return 에서 반환하는 함수가 바로 Unmount시 실행되는 함수이다.

Update

컴포넌트가 업데이트될 때마다 실행하고 싶을 때 사용하는 방법이다.

useEffect(이펙트 함수);

두 번째 인자를 아예 비워놓는다.

이는 컴포넌트가 업데이트될 때마다 실행이 된다.

특정 변수 감지

특정한 변수를 감지하여 이 변수가 변경될 때마다 이펙트 함수를 실행시키고 싶다면

useEffect(이펙트 함수, [감지할 변수1, 감지할 변수2, ...];

로 사용할 수 있다.

useMemo

로직이 복잡하고 연산량이 높은 작업을 수행할 때 사용해서 결과를 반환한다.

사용법

const 저장할 변수 = useMemo(
  	() => {
  		return complexCalcValue(의존성 변수1, 의존성 변수2, ...)
	},
  	[의존성 변수1, 의존성 변수2, ...]
);

이런식으로 사용한다.

뒤의 의존성 변수 배열을 비워놓으면 useEffect처럼 매 렌더링때마다 함수가 실행된다.

빈 배열을 넣으면 useEffect처럼 Mount시 최초 한 번만 저장할 변수에 값을 반환한다.

useCallback

useMemo와 유사하지만, 값을 반환하는 것이 아닌 함수를 반환한다.

사용법

const memoizedCallback = useCallback(
  	() => {
      	doSomething(의존성 변수1, 의존성 변수2, ...);
    },
  	[의존성 변수1, 의존성 변수2, ...]
);

그래서 useCallback(함수, 의존성 배열)useMemo(() => 함수, 의존성 배열)은 똑같은 동작을 한다.

useRef

리액트에서 레퍼런스란 특정 컴포넌트에 접근할 수 있는 객체를 의미한다.

useRef은 바로 이 레퍼런스 객체를 반환한다.

레퍼런스 객체에는 current라는 속성이 있는데, 현재 참조하고 있는 Element를 뜻한다.(refObject.current)

사용법

const refCotainer = useRef(초기값);

위처럼 사용이 된다.

function MyComponent() {
  const myInputRef = useRef(null);

  useEffect(() => {
    // 컴포넌트가 마운트된 후에 실행되는 부분
    myInputRef.current.focus();
  }, []); // 빈 배열을 전달하여 마운트 시에만 실행

  return (
    <div>
      {/* ref를 통해 DOM 요소에 접근 */}
      <input type="text" ref={myInputRef} />
    </div>
  );
}

useRef를 통해 myInputRef를 생성한다.

그리고 이를 input 태그에 ref속성으로 부여한다.

useEffect를 통해 컴포넌트가 최초 마운트 시에 current 속성으로 focust를 하도록 하였다.

이렇게 되면 입력창에 자동으로 커서가 깜빡이게되는(focus가 되었으므로) 컴포넌트가 그려진다.

커스텀 Hook

반복되는 코드를 재사용하려면 어떻게 하는가?

함수로 만들거나 클래스로 만드는 모듈화를 많이 한다.

유명한 책들도 있다. 클린코드 같은...

반복되는 코드는 한 번더 고차원적으로 하나로 묶어 사용하면 가독성과 재사용성에 좋다고 한다.

하지만 무조건 재사용 1번만 한다고 함수로 만들고 이런짓은 하면 오히려 독이 될 수도 있으므로 소신 껏 하는게 좋다.

본론으로 넘어가면 이런 재사용을 위해 커스텀 Hook을 만들어 사용하는 경우가 많다.

원칙과 응용하는 법을 설명하겠다.

원칙

Hook이 지켜야할 원칙이 있다. 최상위 레벨에서만 호출해야 한다. + 리액트 컴포넌트 내부에서만 사용한다.

이는 다음의 말과 같다.

  • 반복문이나 조건문 중복된 함수 안에서 Hook을 호출하면 안된다는 말이다.

  • Hook은 컴포넌트가 렌더링될 때마다 매번 같은 순서로 호출되어야 한다.

무슨 말이냐 하면 예를 들어보겠다.

잘못된 Hook 사용법

function MyComponent() {
  const [name, setName] = useState('milkbottle');
  
  if (name !== ''){
  	useEffect(() => {
    	...
    });
  }
}

name의 결과에 따라 useEffect가 실행되거나 실행되지 않는다.

이는 최상위 레벨에 Hook이 존재하지 않기 때문에(중괄호가 한번 있고 그안에 Hook을 사용해버림) 일어난 일이다.

어떠한 조건에도 상관없이 컴포넌트 내부에선 Hook은 항상 같은 순서, 같은 횟수(useEffect 한 번 사용..)로 호출되어야 하는 말과 같다.

eslint

리액트 컴포넌트가 Hook의 규칙을 잘 지키는지 확인해주는 js 플러그인이다.
https://www.npmjs.com/package/eslint-plugin-react-hooks

사실 코파일럿이 잘되어있어서 써도 된다.

사용법

앞에서 배운 훅들을 응용하여 나만의 커스텀 Hook을 만들 수 있다.

커스텀 Hook은 원칙1(최상위 레벨), 원칙2(리액트 컴포넌트에만), 그리고 use라는 이름으로 시작하도록 정해야한다.

다음은 페이지에 팝업창처럼 상시로 API에서 데이터를 가져와 보여줘야하는 상황이 있다고 하자.

그렇다면 이 데이터를 가져오고 저장하는 것을 재사용해야할 것이다.

import { useState, useEffect } from 'react';

const useFetch = (url) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const result = await response.json();
        setData(result);
      } catch (error) {
        setError(error);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []);

  return { data, loading, error };
};

export default useFetch;

useFetch라는 이름을 커스텀 훅을 만들었다.

여기서는 데이터 그 자체와 로딩중인지 아닌지를 저장하는 boolean 변수, 그리고 에러인지 아닌지를 저장하는 error 변수가 있다.

이를 useState를 통해 상태변수로 가지고 useEffect를 통해 url이 변경되면 fetch가 되도록 정의하였다.

import React from 'react';
import useFetch from './useFetch';

function MyComponent() {
  const { data, loading, error } = useFetch('https://api.example.com/data');

  if (loading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div>
      {/* 데이터를 사용한 UI */}
      <p>Data: {data}</p>
    </div>
  );
}

export default MyComponent;

그리고 이를 import하여 useEffect, useState 처럼 useFetch라는 커스텀 Hook을 사용할 수가 있는 것이다.

0개의 댓글