Programming/React

React 공식문서 정리 - 커스텀 훅으로 로직 재사용하기

한우콩 2023. 12. 29. 00:22

Custom Hooks: Sharing logic between components

커스텀 훅: 컴포넌트간의 로직 공유 

 

컴포넌트 간의 증첩된 로직이 발생한다면..?

=> 커스텀 훅으로 분리할 타이밍!

 

예제에서는 네트워크 상태에 따른 온,오프라인 처리로직을 커스텀 훅으로 재사용하고 있다.

function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {
    function handleOnline() {
      setIsOnline(true);
    }
    function handleOffline() {
      setIsOnline(false);
    }

    window.addEventListener('online', handleOnline);
    window.addEventListener('offline', handleOffline);
    
    return () => {
      window.removeEventListener('online', handleOnline);
      window.removeEventListener('offline', handleOffline);
    };
  }, []);
  
  return isOnline;
}
function StatusBar() {
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? '✅ Online' : '❌ Disconnected'}</h1>;
}
=> 이제는 코드가 어떻게가 아닌 무엇을 할 것인지를 설명하고 있다!
=> 관심사를 분리하여 로직을 추상화했다!

 

Hook names always start with use

훅의 이름은 언제나 use로 시작됩니다. 

 

  1. 컴포넌트 이름은 항상 대문자로 시작
  2. 커스텀 훅스는 항상 use 접두사로 시작

 

=> 이러한 규칙을 통해 React 컴포넌트, 커스텀 훅스에 대해 인지할 수 있고 내부에 state, 또다른 hooks를 호출을 포함할 가능성이 있다.

 

Custom Hooks let you share stateful logic, not state itself

커스텀 훅은 state 자체가 아닌 상태적인 로직(stateful logic)을 공유합니다. 

 

=> 커스텀 훅도 결국엔 함수다. 로직을 재사용하는 것일뿐 내부 상태를 공유하는것이 아님!
=> state를 공유하고 싶다면 lifting state up을 통해 공통 부모 컴포넌트에서 관리해라

 

Passing reactive values between Hooks

훅 사이에 반응형 값 전달하기 

export function useChatRoom({ serverUrl, roomId }) {
  useEffect(() => {
    const options = {
      serverUrl: serverUrl,
      roomId: roomId
    };

    const connection = createConnection(options);
    connection.connect();

    connection.on('message', (msg) => {
      showNotification('New message: ' + msg);
    });
    
    return () => connection.disconnect();
  }, [roomId, serverUrl]);
}
  • ChatRoom 컴포넌트가 다시 렌더링할 때마다 최신 roomId와serverUrl을 Hook에 전달합니다. 
  • 이것이 바로 리렌더링 후 값이 달라질 때마다 Effect가 채팅에 다시 연결되는 이유입니다.
=> Effect또한 커스텀 훅스로 분리가 가능하다! 독립적이기 때문!
export default function ChatRoom({ roomId }) {
  const [serverUrl, setServerUrl] = useState('https://localhost:1234');

  useChatRoom({
    roomId: roomId,
    serverUrl: serverUrl
  });

  //…
}

Passing event handlers to custom Hooks

커스텀훅에게 이벤트 핸들러 전달하기 

  • onReceiveMessage를 커스텀 훅스의 인자로 전달하고싶다.
  • 그러나 onReceiveMessage이 effect 의존성으로 추가되므로 리렌더링 될 때 마다 채팅이 다시 연결됨.
=> 반응형 값에서 제외하고 싶다면 Effect event 를 사용하자!

 

  useChatRoom({

    roomId: roomId,

    serverUrl: serverUrl,

    onReceiveMessage(msg) {

      showNotification('New message: ' + msg);

    }

  });

 

export function useChatRoom({ serverUrl, roomId, onReceiveMessage }) {

  const onMessage = useEffectEvent(onReceiveMessage);

 

  useEffect(() => {

    const options = {

      serverUrl: serverUrl,

      roomId: roomId

    };

    const connection = createConnection(options);

    connection.connect();

    connection.on('message', (msg) => {

      onMessage(msg);

    });

    return () => connection.disconnect();

  }, [roomId, serverUrl]); // ✅ All dependencies declared

                           // ✅ 모든 의존성이 선언됨

}

 

When to use custom Hooks

언제 커스텀 훅을 사용할 것인가

 

  • 중복 코드에 대해 무조건 커스텀 훅으로 추출할 필요는 없다. (약간의 중복 ok)
  • Effect로 외부 시스템과 동기화가 필요하다면 추출을 고려해볼 필요가 있다.

 

function ShippingForm({ country }) {

  const cities = useData(`/api/cities?country=${country}`);

  const [city, setCity] = useState(null);

  const areas = useData(city ? `/api/areas?city=${city}` : null);

  // ...

 

해당 코드 뭔가 좀 이상하다. 첫번째 커스텀 훅스에서 city state에 대한 업데이트랑 무관해보이는데 뭔 상관이지..?

아마 생략된 코드부분의 이벤트 핸들러에서 setter 함수를 호출하고 있을 것 같다.

 

Custom Hooks help you migrate to better patterns

커스텀 훅은 더 나은 패턴으로 마이그레이션하는데 도움을 줍니다. 

 

  • 예시 코드는 컴포넌트가 마운트 될 때 isOnline state가 true라고 가정함
  • 하지만, 네트워크가 오프라인 상태였다면 이는 맞지 않는 상황이다.
  • 런타임이 브라우저일 경우엔  navigator.onLine API로 sync 가능하지만 서버사이드에선 불가능함.
  • useSyncExternalStore를 사용해 외부 시스템 저장소와 동기화하는 방법이 있다.(React v18)

 

As-is

 

export function useOnlineStatus() {

  const [isOnline, setIsOnline] = useState(true);

  useEffect(() => {

    function handleOnline() {

      setIsOnline(true);

    }

    function handleOffline() {

      setIsOnline(false);

    }

    window.addEventListener('online', handleOnline);

    window.addEventListener('offline', handleOffline);

    return () => {

      window.removeEventListener('online', handleOnline);

      window.removeEventListener('offline', handleOffline);

    };

  }, []);

  return isOnline;

}

 

To-be

 

import { useSyncExternalStore } from 'react';

 

function subscribe(callback) {

  window.addEventListener('online', callback);

  window.addEventListener('offline', callback);

  return () => {

    window.removeEventListener('online', callback);

    window.removeEventListener('offline', callback);

  };

}

 

export function useOnlineStatus() {

  return useSyncExternalStore(

    subscribe,

    () => navigator.onLine,  // 클라이언트에서 값을 가져오는 방법

    () => true                        // 서버에서 값을 가져오는 방법

  );

}

 

 

커스텀 훅으로 Effect를 랩핑했을 때 장점..!

 

  1. Effect와의 데이터 흐름을 매우 명확하게 만들 수 있습니다. (커스텀 훅스 추상화를 통한 선언적으로 사용가능)
  2. 컴포넌트가 Effect의 정확한 구현보다는 의도에 집중할 수 있습니다. (관심사의 분리!!)
  3. React가 새로운 기능을 추가할 때 컴포넌트를 변경하지 않고도 해당 Effect를 제거할 수 있습니다. (커스텀 훅스 호출코드를 제거)

참조

https://react-ko.dev/learn/reusing-logic-with-custom-hooks

 

커스텀 훅으로 로직 재사용하기 – React

The library for web and native user interfaces

react-ko.dev