개발자가 스스로 커스텀한 훅, 반복되는 로직을 함수로 뽑아내어 재사용

여러 url을 fetch할 때, 여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때

  1. 상태관리 로직 재활용 가능
  2. 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직을 구현
  3. 함수형으로 작성하기 때문에 보다 명료(e.g. useSomething)
//FriendStatus : 친구가 online인지 offline인지 return하는 컴포넌트
function FriendStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  if (isOnline === null) {
    return "Loading...";
  }
  return isOnline ? "Online" : "Offline";
}

//FriendListItem : 친구가 online일 때 초록색으로 표시하는 컴포넌트
function FriendListItem(props) {
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });

  return (
    <li style={{ color: isOnline ? "green" : "black" }}>{props.friend.name}</li>
  );
}

두 컴포넌트는 정확하게 겹치는 로직이 있다. Custom Hook을 사용하여 로직을 두 컴포넌트에서 공유

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

동일하게 사용되고 있는 부분을 함수로 만듬

Custom Hook 규칙

  1. 함수 이름 앞에 use
  2. 프로젝트 내 hooks 디렉토리에 Custom Hook 위치
  3. 함수는 조건부 함수가 아니어야 함. useFriendStatus Hook은 온라인 상태의 여부를 boolean 타입으로 반환함

Hook 내부에 useState와 같은 React 내장 Hook을 사용하여 작성

일반 함수 내부에서는 React 내장 Hook을 불러 사용할 수 없지만 Custom Hook 에서는 가능

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return "Loading...";
  }
  return isOnline ? "Online" : "Offline";
}

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? "green" : "black" }}>{props.friend.name}</li>
  );
}

Custom Hook을 사용했다고 해서 두 개의 컴포넌트가 같은 state를 공유하는 것은 아님. state는 컴포넌트 내에서 독립적으로 정의 되어 있음


예시


const useFetch = (initialUrl: string) => {
  const [url, setUrl] = useState(initialUrl);
  const [value, setValue] = useState("");

  const fetchData = () => axios.get(url).then(({ data }) => setValue(data));

  useEffect(() => {
    fetchData();
  }, [url]);

  return [value];
};

export default useFetch;

여러 url을 fetch할 때


import { useState, useCallback } from "react";

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  // change
  const onChange = useCallback((e) => {
    const { name, value } = e.target;
    setForm((form) => ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() => setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;

여러 input에 의한 상태 변경을 할 때


실습


component/input.js

function Input({ nameValue, setNameValue }) {
  return (
    <input
      value={nameValue}
      onChange={(e) => setNameValue(e.target.value)}
      type="text"
    />
  );
}

export default Input;

util/useInput.js

import { useState } from "react";

function useInput({ firstNameValue, lastNameValue }) {
  const [nameArr, setNameArr] = useState([]);

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

  return { nameArr, handleSubmit };
}

export default useInput;

App.js

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

export default function App() {
  const [firstNameValue, setFirstNameValue] = useState("");
  const [lastNameValue, setLastNameValue] = useState("");
  const { nameArr, handleSubmit } = useInput({ firstNameValue, lastNameValue });

  return (
    <div className="App">
      <h1>Name List</h1>
      <div className="name-form">
        <form onSubmit={handleSubmit}>
          <div className="name-input">
            <label></label>
            <Input
              setNameValue={setFirstNameValue}
              nameValue={firstNameValue}
            />
          </div>
          <div className="name-input">
            <label>이름</label>
            <Input setNameValue={setLastNameValue} nameValue={lastNameValue} />
          </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>
  );
}

댓글남기기