Section4 React 심화(2)

keepgoing·2023년 3월 22일

코드스테이츠

목록 보기
19/31
post-thumbnail

Custom Hooks

✅ Custom Hooks

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

  • 여러 url을 fetch할 때, 여러 input에 의한 상태 변경 등 반복되는 로직을 동일한 함수에서 작동하게 하고 싶을 때 커스텀 훅을 주로 사용
  1. 상태 관리 로직의 재활용 가능
  2. 클래스 컴포넌트보다 적은 양의 코드로 동일한 로직 구현
  3. 함수형으로 작성하기에 보다 명료하다는 장점(ex. useSomething)

컴포넌트가 존재합니다(React 공식 문서에 존재하는 컴포넌트)

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

FriendStatusFriendListItem 컴포넌트는 정확하게 똑같이 쓰이는 로직이 존재한다.


이 로직을 빼내서 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;
}

두 컴포넌트에서 사용하기 위해 동일하게 사용되고 있는 로직을 분리하여 함수 useFriendStatus로 작성

HOOK 정의 시 규칙
1. Custom Hook 정의 시 함수 이름 앞에 use 붙이기
2. 대개의 경우 프로젝트 내의 hooks 디렉토리에 Custom Hook 위치 시키기
3. Custom Hook으로 만들 때 함수는 조건부 함수가 아니어야 한다.
-> return 하는 값은 조건부여서는 안된다.
-> useFriendStatus Hook은 온라인 상태의 여부를 boolean 타입으로 반환

Custom Hook은 Hook 내부에 useState와 같은 React 내장 Hook을 사용하여 작성할 수 있다.
❗️ 일반 함수 내부에서는 내장 Hook을 불러 사용 불가능하다.


useFriendStatus을 두 컴포넌트에 적용 시키기

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는 독립적으로 정의

✅ 실습

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

분리전

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/util/useFetchData.js
import {useState, useEffect} from "react";

const userFetchData = (url) => {
  const [data, setData]
  
  useEffect(() => {
  	fetch(url, {
      hearders:{
        "Content-Type": "application/json",
        Accept: "application/json",
      },
  })
    .then((response) => {
    	return response.json()
    })
    .then((myJson) => {
      setData(myJson);
    })
    .catch((err) => {
    console.log(err)
    });
  }, [url];
  return data;
}
export default useFetchData;

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

export default function App() {

  const data = useFetchData("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>
  );
}

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

분리 전 : 낮은 가독성

import { useState } from "react";
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>
  );
}

분리 후

profile
매일매일

0개의 댓글