Daily Front_Minhhk

Custom Hooks 본문

Code개발일지

Custom Hooks

Minhhk 2023. 1. 25. 15:59

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

여러 url을 fetch할 때, 여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때 커스텀 훅을 주로 사용합니다. 이를 이용하면

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

 

예시를 살펴보자!!

//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>
  );
}

FriendStatus 컴포넌트는 사용자들이 온라인인지 오프라인인지 확인하고, FriendListItem 컴포넌트는 사용자들의 상태에 따라 온라인이라면 초록색으로 표시하는 컴포넌트입니다.

이 두 컴포넌트는 정확하게 똑같이 쓰이는 로직이 존재하고 있습니다. 이 로직을 빼내서 두 컴포넌트에서 공유할 수는 없을까요? 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을 정의할 때는 일종의 규칙이 필요합니다.

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

 

 Custom 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할 때 쓸 수 있는 useFetch Hook

 

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에 의한 상태 변경을 할 때 쓸 수 있는 useInputs Hooks

 

 

 

🐤실습

 

useFetch() 실습

App.js

import "./styles.css";
import useFetch from "./util/useFetch"
// import datas from "../public/data.json"

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

  return (
    <div className="App">
      <h1>To do List</h1>
      <div className="todo-list">
        {/* fetchData 가 있을때 값을 받아 오겠다 */}
        {fetchData &&
          fetchData.todo.map((el) => {
            return <li key={el.id}>{el.todo}</li>;
          })}
      </div>
    </div>
  );
}

src/util/useFetch.js

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

import { useEffect, useState } from "react";

const useFetch = (fetchUrl) => {
  const [data, setData] = useState();

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

  return data;
};

export default useFetch;

 

 

 

useInput() 실습

src/component/Input.js

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

export default Input;

src/util/useInput.js

import { useState } from "react";

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

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

  // 제출 후 인풋 값 리셋
  const reset = () => {
    setValue('')
  }

  return [value, bind, reset];
}

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() {
  //input에 들어가는 상태값 및 로직을 custom hook으로 만들어봅니다.
  //until 폴더의 useInput.js 파일이 만들어져 있습니다.
  const [firstNameValue, setFirstNameValue, firstReset] = useInput("first");
  const [lastNameValue, setLastNameValue, secondReset] = useInput("last");
  const [nameArr, setNameArr] = useState([]);

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

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

'Code개발일지' 카테고리의 다른 글

컴퓨터 공학 - 구조, 운영체제  (0) 2023.01.27
React.lazy()와 Suspense  (0) 2023.01.26
Hooks & useMemo, useCallback  (1) 2023.01.25
번들링과 웹팩  (0) 2023.01.18
반응형 웹  (0) 2023.01.16