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로 시작됩니다.
- 컴포넌트 이름은 항상 대문자로 시작
- 커스텀 훅스는 항상 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를 랩핑했을 때 장점..!
- Effect와의 데이터 흐름을 매우 명확하게 만들 수 있습니다. (커스텀 훅스 추상화를 통한 선언적으로 사용가능)
- 컴포넌트가 Effect의 정확한 구현보다는 의도에 집중할 수 있습니다. (관심사의 분리!!)
- React가 새로운 기능을 추가할 때 컴포넌트를 변경하지 않고도 해당 Effect를 제거할 수 있습니다. (커스텀 훅스 호출코드를 제거)
참조
https://react-ko.dev/learn/reusing-logic-with-custom-hooks
'Programming > React' 카테고리의 다른 글
Next.js 14 - z.com(1) (0) | 2024.04.16 |
---|---|
State management - 상태관리 (0) | 2024.03.09 |
React 공식문서 정리 - Removing Effect DependenciesEffect (의존성 제거하기) (1) | 2023.12.28 |
React 공식문서 정리 - ref로 DOM 조작하기 (1) | 2023.12.19 |
배열 key props (0) | 2023.04.28 |