[React] 커스텀 훅(Custom Hooks)

rsuubinn·2023년 3월 28일
0

React

목록 보기
7/7

👊🏻 커스텀 훅(Custom Hooks)

: 개발자가 스스로 커스텀한 훅을 의미하며, 이를 이용해 반복되는 로직을 함수로 뽑아내어 재사용 할 수 있다.

커스텀 훅 정의할 때 규칙

  • Custom Hook을 정의할 때는 함수 이름 앞에 use 를 붙이는 것이 규칙이다.
  • 대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook을 위치시킨다.
  • Custom Hook 으로 만들 때 함수는 조건부 함수가 아니어야 한다. 즉 return 하는 값은 조건부여서는 안된다.

실습

✔ useEffect 로직 분리하기

// src/App.js
import "./styles.css";
import { useEffect, useState } from "react";

export default function App() {
  const [data, setData] = useState();

  useEffect(() => {
    fetch("data.json", {
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json"
      }
    })
      .then((response) => {
        return response.json();
      })
      .then((myJson) => {
        setData(myJson);
      })
      .catch((error) => {
        console.log(error);
      });
  }, []);

  return (
    <div className="App">
      <h1>To do List</h1>
      <div className="todo-list">
        {data &&
          data.todo.map((el) => {
            return <li key={el.id}>{el.todo}</li>;
          })}
      </div>
    </div>
  );
}
// src/utill/hooks.js

//app.js에서 함수를 가져와서 Custom hook을 만들어 봅시다.
//hooks.js의 이름도 알맞게 바꿔주세요.

const hooks = (fetchUrl) => {};

export default hooks;

useEffect hook을 이용한 로직은 반복되는 로직이 많다.
특히 API를 통해 데이터를 받아와 처리하는 로직은 반복적일 수 밖에 없다.
이러한 로직을 custom hook으로 만들어 분리해보자.

// src/util/useFetch.js

// hook.js -> useFetch.js 파일 이름 변경

import { useEffect, useState } from "react";

const useFetch = (fetchUrl) => {
  const [value, setValue] = useState();

  useEffect(() => {
    fetch(fetchUrl, {
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json"
      }
    })
      .then((response) => {
        return response.json();
      })
      .then((myJson) => {
        setValue(myJson);
      })
      .catch((error) => {
        console.log(error);
      });
  }, [fetchUrl]);
  return value;
};

export default useFetch;
// src/App.js

import "./styles.css";
import useFetch from "./util/useFetch";

export default function App() {
  const data = useFetch("data.json");

  return (
    <div className="App">
      <h1>To do List</h1>
      <div className="todo-list">
        {data &&
          data.todo.map((el) => {
            return <li key={el.id}>{el.todo}</li>;
          })}
      </div>
    </div>
  );
}

✔ input 로직 분리하기

// src/App.js
import { useState } from "react";
import useInput from "./util/useInput";
import Input from "./component/Input";
import "./styles.css";

export default function App() {
  //input에 들어가는 상태값 및 로직을 custom hook으로 만들어봅니다.
  //until 폴더의 useInput.js 파일이 만들어져 있습니다.
  const [firstNameValue, setFirstNameValue] = useState("");
  const [lastNameValue, setLastNameValue] = useState("");
  const [nameArr, setNameArr] = useState([]);

  const handleSubmit = (e) => {
    e.preventDefault();
    setNameArr([...nameArr, `${firstNameValue} ${lastNameValue}`]);
  };

  return (
    <div className="App">
      <h1>Name List</h1>
      <div className="name-form">
        <form onSubmit={handleSubmit}>
          <div className="name-input">
            <label></label>
            <input
              value={firstNameValue}
              onChange={(e) => setFirstNameValue(e.target.value)}
              type="text"
            />
          </div>
          <div className="name-input">
            <label>이름</label>
            <input
              value={lastNameValue}
              onChange={(e) => setLastNameValue(e.target.value)}
              type="text"
            />
          </div>
          <button>제출</button>
        </form>
      </div>
      <div className="name-list-wrap">
        <div className="name-list">
          {nameArr.map((el, idx) => {
            return <p key={idx}>{el}</p>;
          })}
        </div>
      </div>
    </div>
  );
}
// src/util/useInput.js

//이 곳에 input custom hook을 만들어 보세요.
//return 해야 하는 값은 배열 형태의 값이어야 합니다.

function useInput() {
  return [];
}

export default useInput;
// src/component/Input.js

function Input() {
  //input을 컴포넌트로 만들어봅니다.
  //input도 컴포넌트도 만들게 되면 로직을 조금 더 고민을 해야 합니다.
  //고민을 한 번 해보고, 시도 해보세요!
}

export default Input;

input 또한 반복적으로 사용되는 로직을 가지고 있는 컴포넌트이다.
이런 식으로 앱 내에 반복적으로 사용되고 관리되는 로직은 많다.
이런 컴포넌트 또한 custom hook을 이용하여 로직을 분리하여 관리할 수 있다.
App 컴포넌트에 들어 있는 input Input 컴포넌트로 바꾼 후, custom hook을 이용하여 input 로직을 분리해보자.

// src/App.js

import { useState } from "react";
import useInput from "./util/useInput";
import shortid from "shortid";
import Input from "./component/Input";
import "./styles.css";

export default function App() {
  //input에 들어가는 상태값 및 로직을 custom hook으로 만들어봅니다.
  //until 폴더의 useInput.js 파일이 만들어져 있습니다.
  const [nameArr, setNameArr] = useState([]);

  const [firstBind, firstReset] = useInput("");
  const [lastBind, lastReset] = useInput("");

  const handleSubmit = (e) => {
    e.preventDefault();
    let userName = {
      id: shortid.generate(),
      first: firstBind.value,
      last: lastBind.value
    };
    setNameArr([...nameArr, userName]);
    firstReset();
    lastReset();
  };

  return (
    <div className="App">
      <h1>Name List</h1>
      <div className="name-form">
        <form onSubmit={handleSubmit}>
          <Input label={"성"} {...firstBind} />
          <Input label={"이름"} {...lastBind} />
          <button>제출</button>
        </form>
      </div>
      <div className="name-list-wrap">
        <div className="name-list">
          {nameArr.map((el) => {
            return (
              <p key={el.id}>
                {el.first}
                {el.last}
              </p>
            );
          })}
        </div>
      </div>
    </div>
  );
}
// src/util/useInput.js

//이 곳에 input custom hook을 만들어 보세요.
//return 해야 하는 값은 배열 형태의 값이어야 합니다.

import { useCallback, useState } from "react";

function useInput(initialValue) {
  const [value, setValue] = useState(initialValue);

  const reset = useCallback(() => setValue(initialValue), [initialValue]);

  const bind = {
    value,
    onChange: useCallback((e) => {
      const { value } = e.target;
      setValue(value);
    }, [])
  };

  return [bind, reset];
}

export default useInput;
// src/component/Input.js

function Input({ label, value, onChange }) {
  //input을 컴포넌트로 만들어봅니다.
  //input도 컴포넌트도 만들게 되면 로직을 조금 더 고민을 해야 합니다.
  //고민을 한 번 해보고, 시도 해보세요!

  return (
    <div className="name-input">
      <label>{label}</label>
      <input value={value} onChange={onChange} type="text" />
    </div>
  );
}

export default Input;

profile
@rsuubinn

0개의 댓글