Home [React] Custom Hooks으로 로직 재사용하기!
Post
Cancel

[React] Custom Hooks으로 로직 재사용하기!

시작하면서..

Hook 이란 개념은 사실 React 16.8v 에도 있던 기능이다. 그리고 이런 훅은 예전부터 사용해왔다. 예를 들어, 컴포넌트의 state 를 관리하는 useState 나, 콜백함수를 전달하는 useEffect 같은 훅 말이다.

하지만 최근들어 custom hook 을 사용하여 로직을 재사용하는 방법을 선호한다는 얘기를 들었다. 원티드 프리온보딩을 하면서 다른 프론트엔드 신입 개발자들과 프로젝트를 하는 중 로직을 재사용하는 커스텀 훅에 관해서 보았다. 페이지 컴포넌트를 매우 단순화 할 뿐 아니라, 재사용 할 수 있는 기능을 보고 매료되었다. 더 공부하고 싶다는 생각이 들어서 이번기회에 정리해보려 한다.

다음은 Custom Hook 에 대해 공식문서를 보고 정리한 내용이다. 앞으로 이 블로그를 쓰면서 배워야 할 목표를 정한다.

Custom Hook 을 이용해서 다음을 배워보자:

  • cutom hook 이 뭔지, 나만의 hook 을 작성하는 방법
  • 컴포넌트 사이에서 로직을 재사용하는 방법
  • custom hook 을 설계하는 방법과 이름 짓는 방법
  • 커스텀 훅을 언제, 왜 뽑는지에 대하여

Custom Hook: Sharing logic between components

내가 네트워크 요청에 크게 의존하는 앱을 만들려고한다. 나는 내 앱을 이용하는 유저에게 네트워크 상태에 대해서 경고하려고 한다. 컴포넌트에는 두개의 일이 필요할 것 같은데:

  1. 네트워크가 온라인인지를 추적하는 상태 하나
  2. 전역 online 과 offline 이벤트를 구독하는 effect, 그리고 state 를 업데이트를 해줘야 함.

이 두개는 네트워크 상태를 컴포넌트에 싱크로나이즈 할 것이다. 다음과 같은 예를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// App.js

import { useState, useEffect } from 'react';

export default function SstatusBar() {
	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 <h1>{isOnline ? 'Online' : 'Disconnected'}</h1>

그리고 동일한 로직을 사용해서 다른 컴포넌트를 만들어 보자. 저장 버튼을 만들건데 online 이면 save 하고, 오프라인이면 세이브 대신 Reconnecting 하는 버튼이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { useState, useEffect } from 'react';

export default function SaveButton() {
	// isOnline 이라는 state 와, online offline 을 구분하는 effect 함수는 중복된다.
	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);
    };
  }, []);
}

	function handleSaveClick() {
		console.log('Progress saved');
	}

return (
	<button disabled={!isOnline} onClick={handleSaveClick}>
	{isOnline ? 'Save progress' : 'Reconnecting...'}
	</button>
	)
}

이 컴포넌트에서는 네트워크가 꺼지면, 버튼의 모습이 바뀔 것이다.

이 두개의 컴포넌트는 모두 잘 작동하지만 동일한 로직이 중복되어 사용되고 있다. 커스텀 훅으로 중복되게 사용되는 로직을 줄여보자.

컴포넌트에서 custom hook 추출하기

중복된 코드를 다음과 같이 줄여보자:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function StatusBar() {
  // 실제 필요한 state 만 커스텀 훅에서 추출한 모습.
  // 화면을 바꾸는 요소인 state 를 커스텀 훅에서 모두 처리하고 결과값만 가져온 모습이다.
  const isOnline = useOnlineStatus();
  return <h1>{isOnline ? "Online" : "Disconnected"}</h1>;
}

function SaveButton() {
  const isOnline = useOnlineStatus();

  function handleSaveClick() {
    console.log("Progress saved");
  }

  return (
    <button disabled={!isOnline} onClick={handleSaveClick}>
      {isOnline ? "Save progress" : "Reconnecting..."}
    </button>
  );
}

이렇게 하면, 선언형 으로 코드를 더 짤 수 있다. 실제 저 안에 로직은 중요하지 않고(보여주지 않고) 결과만 보여주는 선언형인 것이다.

이것에 대해 정리한 내 블로그가 있다.

관심사의 분리

커스텀 훅으로 만든 useOnlineStatus() 를 살펴보자. 아까 위에서 적었던 중복된 로직을 포함시키고, 실제로 필요한 isOnline state 만 리턴하는 함수다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;

이렇게 작성하는 경우

  • 무엇을 하고싶은지를 더 명확히 표현이 가능(객체 지향에 가까워짐)
  • jsx, tsx 컴포넌트가 매우 간단해짐

커스텀 훅은 상태 자체를 공유하지 않고, 상태 저장 논리를 공유한다.

두개는 같은 useonlineStatus () 라는 커스텀 훅을 공유한다. 따라서 마치 이것은 같은 state 를 공유하는 것 처럼 보이지만, 명백히 다른 상태를 관리하는 것이다.

–> 상태공유가 아닌, 상태관리를 공유한다!

다음과 같은 예를 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// useFormInput.js

import {useState } from 'react';

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

	function handleChange(e) {
		setValue(e.target.value);
	}

	const inputProps = {
		value,
		onChange: handleChange
	}

	return inputProps;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {useFormInput} from
./useFormInput.js';

export default function Form() {
	const firstNameProps = useFormInput('Mary');
	const lastNameProps = useFormInput('Poppins');

	return(
		<>
		<label>
			First name:
			<input {...firstNameProps} />
		</label>
		<label>
			Last name:
			<input {...lastNameProps} />
		</label>
		<p><b>Good morning, {firstNameProps.value} {lastNameProps.value}.</b></p>
	</>
);

위 예시를 보면 firstName 과 lastNameprops 는 커스텀 훅을 두번 호출한다. 즉, 두개는 별개의 state 를 사용하는 것이다.

만약, 여러 구성 요소간에 상태 자체를 공유해야하는 경우는 어쩔 수 없이 상위 컴포넌트에서 하위 컴포넌트로 전달해야 될 것이당.

Hooks 간에 반응 값 전달(passing reactive values between hooks)

컴포넌트가 매 re-render 가 될 때마다 커스텀 훅에 있는 코드는 재호출 될 것이다. 이것이 커스텀 훅이 순수해야 하는 이유다. 커스텀 훅의 코드를 기존 컴포넌트 본문의 일부라고 생각하면 된다!

커스텀 훅은 컴포넌트와 함께 다시 렌더링 되기 때문에 항상 최신 props 및 state 를 수신한다.

출처

react.dev 공식문서에서 배우기

This post is licensed under CC BY 4.0 by the author.

[JS] 이터레이션 프로토콜

[React] 프론트엔드 성능측정에 대하여