새소식

프론트엔드 공부/React

Custom Hooks

  • -

Custom Hooks란?

개발자가 스스로 커스텀한 Hook을 의미하며, 커스텀 훅(Custom Hook)은 함수형 컴포넌트에서 상태 관리 로직을 재사용할 수 있는 JavaScript 함수입니다. 커스텀 훅을 사용하면 컴포넌트 내부의 상태 관리 로직을 추출하여 별도의 함수로 만들어 여러 컴포넌트에서 재사용할 수 있습니다.
커스텀 훅은 고차 컴포넌트(Higher-order Components)나 렌더 프롭(Render Props)을 사용하지 않고도 컴포넌트 간에 기능을 공유하는 방법입니다.
주로, 여러 url을 fetch할 때여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때 사용합니다.
Custom Hook을 이용한다면 아래의 장점이 있습니다.

  • 상태관리 로직의 재활용이 가능하고
  • 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현할 수 있으며
  • 함수형으로 작성하기 때문에 보다 명료하다는 장점이 있습니다

custom hook을 이용하여 useEffect 로직 분리하기

// App.js

import useFetch from "./util/useFetch";
import './fetch.css';

//useFetch, useInput, useForm : custom hook
export default function App() {
  const fetchData = useFetch("data.json");

  return (
    <div className="todo-wrap">
      <h1 className="todo-title">To do List</h1>
      <div className="todo-list">
        {data &&
          data.todo.map((el) => {
            return <li key={el.id}>{el.todo}</li>;
          })}
      </div>
    </div>
  );
}
// .util/useFetch.js
import { useEffect, useState } from "react";

// fetchUrl을 인자로 받아 useFetch 함수를 선언합니다.
const useFetch = (fetchUrl) => {
  // data와 setData를 useState로 정의합니다.
  const [data, setData] = useState();

  useEffect(() => {
    // useEffect를 사용하여 데이터를 가져오는 fetch 요청을 보냅니다.
    fetch(fetchUrl, {
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json"
      }
    })
      .then((response) => {
        // response를 json 형태로 변환합니다.
        return response.json();
      })
      .then((myJson) => {
        // 변환된 데이터를 setData로 업데이트합니다.
        setData(myJson);
      })
      .catch((error) => {
        // 오류가 발생할 경우 console에 출력합니다.
        console.log(error);
      });
  }, [fetchUrl]);

  // 데이터를 반환합니다.
  return data;
};

// useFetch 함수를 내보냅니다.
export default useFetch;

React Hook을 사용하여 API에서 데이터를 가져오는 기능을 구현한 useFetch 함수입니다. useFetch 함수는 fetchUrl을 인자로 받아 데이터를 가져옵니다.
useState를 사용하여 데이터를 저장하고, useEffect를 사용하여 데이터를 가져오는 fetch 요청을 보냅니다. 그리고, 받아온 response를 json 형태로 변환한 후 setData로 데이터를 업데이트합니다.
마지막으로, useFetch 함수는 데이터를 반환합니다. 이렇게 구현된 useFetch 함수는 다른 React 컴포넌트에서 호출하여 사용할 수 있습니다.

[fetchUrl]  의존성배열

React Hook의 useEffect는 첫 번째 인자로 함수를 받습니다. 두 번째 인자로는 의존성 배열을 받습니다. 의존성 배열에는 해당 함수가 실행되는 조건을 지정할 수 있습니다. 배열에 지정된 값이 변경되면 해당 함수가 다시 실행됩니다.
useFetch 함수에서는 fetchUrl을 의존성 배열에 넣어주고 있습니다. 이렇게 함으로써 fetchUrl이 변경될 때마다 useEffect 함수가 다시 실행되도록 만들어 줍니다. fetchUrl이 변경되면 데이터를 다시 가져와서 업데이트해야 하기 때문입니다.

의존성 배열(Dependency Array)은 React Hook의 useEffect 함수에서 사용되는 배열입니다.

useEffect 함수는 첫 번째 인자로 함수를 받습니다. 이 함수는 컴포넌트가 마운트될 때, 언마운트될 때, 혹은 의존성 배열에 지정된 값이 변경될 때 실행됩니다.
두 번째 인자로는 의존성 배열을 받습니다. 이 배열에는 해당 함수가 실행되는 조건을 지정할 수 있습니다. 배열에 지정된 값이 변경되면 해당 함수가 다시 실행됩니다.
의존성 배열을 사용하면 특정 값을 감시하면서 해당 값이 변경될 때마다 함수를 다시 실행할 수 있습니다. 이를 통해 컴포넌트가 효율적으로 렌더링될 수 있습니다. 불필요한 렌더링을 방지하고, 필요한 값만 업데이트할 수 있도록 해줍니다.
의존성 배열은 배열 안에 포함된 값이나 객체가 변경되었는지를 판단할 때 얕은 비교(shallow comparison)를 합니다. 따라서, 객체나 배열을 의존성 배열에 넣을 때는 주의해야 합니다. 객체나 배열이 변경되지 않아도 내부 값이 변경될 경우 함수가 다시 실행될 수 있습니다. 이 경우 불필요한 렌더링이 발생할 수 있습니다.

만약 빈 배열([])을 의존성 배열에 넣어주면, useEffect 함수는 컴포넌트가 마운트될 때만 실행됩니다. 따라서 fetchUrl이 변경되어도 useEffect 함수가 다시 실행되지 않습니다. 이 경우 데이터가 업데이트되지 않을 수 있습니다.

번외) 내보내기 방식의 차이가 있는가?

일반적으로 export default function 방식은 파일에서 하나의 주요 기능 또는 컴포넌트를 내보내는 데 사용됩니다. 예를 들어 React 애플리케이션에서 App.js 파일은 애플리케이션의 주요 컴포넌트를 내보내는 데 사용됩니다.
반면에 export default 키워드 뒤에 오는 변수나 함수는 해당 파일에서 여러 개의 기능 또는 함수를 내보낼 때 사용됩니다. 예를 들어 React에서 커스텀 훅(useState, useEffect 등)을 작성할 때는 한 파일에서 여러 개의 훅을 내보내기 때문에 export default 키워드를 사용합니다.


custom hook을 이용하여 input 로직 분리하기

// input/CustomInputExcercise.js
// React와 useState Hook을 import 합니다.
import { useState } from "react";
// useInput custom Hook을 import 합니다.
import useInput from "./util/useInput";
// Input component와 CSS를 import 합니다.
import Input from "./component/Input";
import "./styles.css";

const CustomInputExcercise = () => {
  
  // useInput custom Hook을 사용해 fistValue, lastValue state를 생성합니다.
  // 각각의 state는 초기값으로 빈 문자열("")을 가지고 있습니다.
  // useInput custom Hook에서는 각 state에 대한 onChange 이벤트 핸들러, value, reset 함수를 반환합니다.
  const [fistValue, firstBind, firstReset] = useInput("");
  const [lastValue, lastBind, lastReset] = useInput("");

  // nameArr state를 생성합니다.
  // 초기값으로 빈 배열([])을 가지고 있습니다.
  const [nameArr, setNameArr] = useState([]);

  // handleSubmit 함수를 생성합니다.
  // 이 함수는 폼 제출(submit) 이벤트가 발생했을 때 호출됩니다.
  // 이 함수는 입력된 성과 이름을 배열에 추가하고,
  // 입력 필드들의 값을 초기화합니다.
  const handleSubmit = (e) => {
    e.preventDefault();
    setNameArr([...nameArr, `${fistValue} ${lastValue}`]);
    firstReset();
    lastReset();
  };

  // JSX를 반환합니다.
  return (
    <div className="Input-wrap">
      <h1>Name List</h1>
      <div className="name-form">
        <form onSubmit={handleSubmit}>
          <Input labelText={"성"} value={firstBind} />
          <Input labelText={"이름"} value={lastBind} />
          <button>제출</button>
        </form>
      </div>
      <div className="name-list-wrap">
        <div className="name-list">
          {/* nameArr 배열의 각 요소를 map 함수를 사용해 출력합니다. */}
          {nameArr.map((el, idx) => {
            return <p key={idx}>{el}</p>;
          })}
        </div>
      </div>
    </div>
  );
}
export default CustomInputExcercise;
// Input 컴포넌트를 정의합니다.
function Input({ labelText, value }) {
  // JSX를 반환합니다.
  return (
    <div className="name-input">
      {/* label 요소에 labelText를 표시합니다. */}
      <label>{labelText}</label>
      {/* input 요소에 value props를 펼쳐서 전달합니다. */}
      <input {...value} type="text" />
    </div>
  );
}

// Input 컴포넌트를 내보냅니다.
export default Input;

Input 컴포넌트는 labelText와 value props를 전달받아 다음과 같은 역할을 수행합니다:

  • labelText를 label 요소에 표시합니다.
  • value props를 input 요소에 전달합니다.
  • 사용자의 입력에 따라 value props를 업데이트합니다.
import { useState } from "react";

function useInput(initialValue) {
  // value 상태와 그 값을 변경하는 setValue 함수를 선언합니다.
  const [value, setValue] = useState(initialValue);

  // value와 그 값을 변경하는 onChange 함수를 반환합니다.
  const bind = {
    value,
    onChange: (e) => {
      setValue(e.target.value);
    }
  };

  // value 값을 초기화하는 reset 함수를 반환합니다.
  const reset = () => {
    setValue(initialValue);
  };
  
  // value, bind, reset을 원소로 가지는 배열을 반환합니다.
  return [value, bind, reset];
}

export default useInput;

useInput 훅은 다음과 같은 역할을 합니다:

  • useState 훅을 사용하여 value 상태와 그 값을 변경하는 setValue 함수를 선언합니다.
  • value와 setValue 함수를 가진 객체를 반환하는 bind를 정의합니다.
  • value 값을 초기화하는 reset 함수를 정의합니다.
  • value, bind, reset을 원소로 가지는 배열을 반환합니다.
bind시킬때  value 가 필요한 이유
const bind = { 
    value, 
    onChange: (e) => { setValue(e.target.value); } 
};

useInput 훅에서 bind 객체는 value와 onChange 속성을 포함하고 있습니다.
value는 input 요소의 현재 값입니다. onChange은 input 요소의 값이 변경될 때마다 호출되는 콜백 함수입니다. 이 함수는 이벤트 객체를 전달받아 setValue 함수를 사용하여 value 상태를 업데이트합니다.
따라서 value 속성이 bind 객체에 포함되어 있어야만 input 요소에서 value를 사용할 수 있습니다. 그렇기 때문에 value 부분이 필요합니다. 만약 value를 제외하면 input 요소에서 현재 값을 가져올 수 없습니다.

'프론트엔드 공부 > React' 카테고리의 다른 글

[React snippets] 컴포넌트 종류별 단축키(숏컷)  (0) 2023.04.14
useMemo & useCallback  (1) 2023.03.22
Redux Toolkit  (0) 2023.02.27
[React] 상태 관리 종합퀴즈  (0) 2023.02.27
Redux  (0) 2023.02.24
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.