본문 바로가기

React

React Hooks 톺아보기 3 - 커스텀 Hooks 작성하기

반응형

React의 커스텀 Hooks 작성법과 활용 사례를 심층적으로 다룹니다. 기본 Hooks를 넘어 재사용 가능한 로직을 구현하고, 프로젝트의 효율성을 높이는 커스텀 Hooks 작성 방법을 알아보세요.

 

고급 Hooks에서 커스텀 Hooks로

지난 두 편의 시리즈 "React Hooks 톺아보기 1 - 기본 Hooks 이해하기""React Hooks 톺아보기 2 - 고급 Hooks 활용법"에서는 React Hooks의 기본 개념과 고급 Hooks인 useReducer, useMemo, useCallback, useRef 등을 살펴보았습니다. 이제 이 모든 지식을 바탕으로, 재사용 가능한 로직을 구현하고 코드의 중복을 줄일 수 있는 커스텀 Hooks 작성법에 대해 알아보겠습니다. 커스텀 Hooks는 프로젝트의 유지보수성과 확장성을 크게 향상시켜 주는 강력한 도구입니다.

 

커스텀 Hooks의 이해

커스텀 Hooks란?

커스텀 Hooks는 React의 기본 Hooks를 조합하여 특정 기능을 수행하는 재사용 가능한 함수입니다. 이를 통해 컴포넌트 간에 공통된 로직을 쉽게 공유할 수 있으며, 코드의 가독성과 유지보수성을 높일 수 있습니다. 커스텀 Hooks는 일반적으로 use로 시작하는 이름을 가지며, React의 Hook 규칙을 준수해야 합니다.

커스텀 Hooks의 필요성

대규모 애플리케이션에서는 동일한 상태 관리 로직이나 사이드 이펙트 처리가 여러 컴포넌트에서 반복될 수 있습니다. 이때, 커스텀 Hooks를 사용하면 이러한 중복 코드를 줄이고, 로직을 중앙에서 관리할 수 있어 개발 효율성을 높일 수 있습니다.

 

커스텀 Hooks 작성하기

기본 구조

커스텀 Hooks는 함수로 작성되며, 내부에서 기본 Hooks를 호출할 수 있습니다. 예를 들어, 데이터 패칭 로직을 커스텀 Hook으로 분리해 보겠습니다.

예제: 데이터 패칭을 위한 커스텀 Hook

import { useState, useEffect } from 'react';

function useFetch(url) {
	const [data, setData] = useState(null);
	const [loading, setLoading] = useState(true);
	const [error, setError] = useState(null);
    
	useEffect(() => {
		let isMounted = true;
		
        async function fetchData() {
			try {
				const response = await fetch(url);

				if (!response.ok) throw new Error('네트워크 응답에 문제가 있습니다.');

				const result = await response.json();
                
				if (isMounted) {
					setData(result);
					setLoading(false);
				}
			} catch (err) {
				if (isMounted) {
					setError(err);
					setLoading(false);
				}
			}
		}

		fetchData();

		return () => {
			isMounted = false;
		};
	}, [url]);

	return { data, loading, error };
}

export default useFetch;

 

설명

  • useFetch: 주어진 URL에서 데이터를 패칭하는 커스텀 Hook입니다.
  • 상태 관리: data, loading, error 상태를 관리합니다.
  • useEffect: URL이 변경될 때마다 데이터를 다시 패칭합니다.
  • 정리 함수: 컴포넌트가 언마운트될 때 isMounted를 false로 설정하여 메모리 누수를 방지합니다.

 

커스텀 Hooks 활용 예제

예제: 사용자 인증 상태 관리

import { useState, useEffect } from 'react';

function useAuth() {
	const [user, setUser] = useState(null);
	const [authLoading, setAuthLoading] = useState(true);
    
	useEffect(() => {
		// 예시: 로컬 스토리지에서 사용자 정보 가져오기
		const storedUser = localStorage.getItem('user');
        
		if (storedUser) {
			setUser(JSON.parse(storedUser));
		}

		setAuthLoading(false);
	}, []);

	const login = (userData) => {
		setUser(userData);
		localStorage.setItem('user', JSON.stringify(userData));
	};
    
	const logout = () => {
		setUser(null);
		localStorage.removeItem('user');
	};

	return { user, authLoading, login, logout };
}

export default useAuth;

 

설명

  • useAuth: 사용자 인증 상태를 관리하는 커스텀 Hook입니다.
  • 상태 관리: user와 authLoading 상태를 관리합니다.
  • login/logout 함수: 사용자 로그인과 로그아웃을 처리하며, 로컬 스토리지에 사용자 정보를 저장하거나 삭제합니다.

커스텀 Hooks의 규칙

  • use로 시작: 모든 커스텀 Hook은 use로 시작해야 합니다. 이는 React가 Hook 규칙을 적용하는 데 필요합니다.
  • Hook 규칙 준수: 커스텀 Hooks는 컴포넌트 최상위에서만 호출되어야 하며, 조건문이나 반복문 내부에서 호출해서는 안 됩니다.
  • 재사용 가능: 커스텀 Hooks는 재사용 가능한 로직을 캡슐화해야 합니다. 특정 컴포넌트에 종속되지 않아야 합니다.

 

커스텀 Hooks 최적화 전략

의존성 관리

커스텀 Hooks 내에서 사용하는 모든 외부 변수는 의존성 배열에 포함시켜야 합니다. 이를 통해 Hook이 의도한 대로 동작하며, 불필요한 재실행을 방지할 수 있습니다.

useEffect(() => {
	// 효과 수행
}, [dependency1, dependency2]);

 

메모이제이션

useMemo와 useCallback을 활용하여 커스텀 Hooks 내부에서 생성되는 함수나 값을 메모이제이션함으로써 성능을 최적화할 수 있습니다.

import { useMemo } from 'react';

function useCustom() {
	const memoizedValue = useMemo(() => computeExpensiveValue(), [dependencies]);
	return memoizedValue;
}

 

에러 핸들링

커스텀 Hooks 내에서 발생할 수 있는 모든 에러를 적절히 처리하여 애플리케이션의 안정성을 높여야 합니다.

try {
	// 로직 수행
} catch (error) {
	// 에러 처리
}

 

실전 예제: 복합적인 커스텀 Hooks 구현

예제: 폼 관리 커스텀 Hook

복잡한 폼 상태와 유효성 검사를 관리하는 커스텀 Hook을 작성해보겠습니다.

useForm 커스텀 Hook

import { useState, useCallback } from 'react';

function useForm(initialValues, validate) {
	const [values, setValues] = useState(initialValues);
	const [errors, setErrors] = useState({});
	const [isSubmitting, setIsSubmitting] = useState(false);
	
    const handleChange = useCallback((e) => {
		const { name, value } = e.target;
		setValues((prevValues) => ({ ...prevValues, [name]: value }));
	}, []);

	const handleSubmit = useCallback(
		(e) => {
			e.preventDefault();
			const validationErrors = validate(values);
			setErrors(validationErrors);
			setIsSubmitting(true);
		},
		[validate, values]
	);
    
	return {
		values,
		errors,
		isSubmitting,
		handleChange,
		handleSubmit,
		setValues,
		setErrors,
		setIsSubmitting,
	};
}

export default useForm;

 

설명

  • useForm: 폼의 상태와 유효성 검사를 관리하는 커스텀 Hook입니다.
  • 상태 관리: values, errors, isSubmitting 상태를 관리합니다.
  • handleChange: 입력 필드의 변경을 처리합니다.
  • handleSubmit: 폼 제출 시 유효성 검사를 수행하고, 제출 상태를 업데이트합니다.

폼 컴포넌트 사용 예제

import React, { useEffect } from 'react';
import useForm from './useForm';

function validate(values) {
	let errors = {};

	if (!values.username) errors.username = '사용자 이름을 입력해주세요.';
	
    if (!values.email) {
		errors.email = '이메일을 입력해주세요.';
	} else if (!/\S+@\S+\.\S+/.test(values.email)) {
		errors.email = '유효한 이메일을 입력해주세요.';
	}
    
	if (!values.password) errors.password = '비밀번호를 입력해주세요.';
    
	return errors;
}

function RegistrationForm() {
	const {
		values,
		errors,
		isSubmitting,
		handleChange,
		handleSubmit,
		setIsSubmitting,
	} = useForm({ username: '', email: '', password: '' }, validate);

	useEffect(() => {
		if (isSubmitting && Object.keys(errors).length === 0) {
			// 폼 제출 로직
			console.log('폼 제출 성공:', values);
			setIsSubmitting(false);
		}
	}, [errors, isSubmitting, values, setIsSubmitting]);
    
	return (
		<form onSubmit={handleSubmit}>
			<div>
				<label>사용자 이름:</label>
				<input
					type="text"
					name="username"
					value={values.username}
					onChange={handleChange}
				/>
				{errors.username && <p style={{ color: 'red' }}>{errors.username}</p>}
			</div>
			<div>
				<label>이메일:</label>
				<input
					type="email"
					name="email"
					value={values.email}
					onChange={handleChange}
				/>
				{errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
			</div>
			<div>
				<label>비밀번호:</label>
				<input
					type="password"
					name="password"
					value={values.password}
					onChange={handleChange}
				/>
				{errors.password && <p style={{ color: 'red' }}>{errors.password}</p>}
			</div>
			<button type="submit" disabled={isSubmitting}>
				제출
			</button>
		</form>
	);
}

export default RegistrationForm;

 

설명

  • validate 함수: 폼의 입력값을 검증하여 오류 메시지를 반환합니다.
  • RegistrationForm 컴포넌트: useForm 커스텀 Hook을 사용하여 폼 상태와 유효성 검사를 관리합니다. 제출 시 유효성 검사를 통과하면 폼 데이터를 콘솔에 출력합니다.

 

커스텀 Hooks 최적화 전략

코드 재사용성과 모듈화

커스텀 Hooks를 작성할 때는 특정 기능을 캡슐화하여 여러 컴포넌트에서 재사용할 수 있도록 해야 합니다. 이를 통해 코드의 중복을 줄이고, 유지보수성을 높일 수 있습니다.

테스트 용이성

커스텀 Hooks는 독립적인 함수로 작성되기 때문에, 별도로 테스트하기 용이합니다. Jest와 React Testing Library 등을 활용하여 Hook의 동작을 검증할 수 있습니다.

성능 최적화

커스텀 Hooks 내에서 불필요한 렌더링을 방지하기 위해 useMemo와 useCallback을 적절히 사용해야 합니다. 또한, 의존성 배열을 정확히 설정하여 Hook이 의도한 대로 동작하도록 해야 합니다.

 

마무리하며

이번 글에서는 커스텀 Hooks의 개념과 작성법, 활용 사례에 대해 심층적으로 살펴보았습니다. 커스텀 Hooks를 통해 코드의 재사용성을 높이고, 프로젝트의 유지보수성을 향상시킬 수 있습니다. React Hooks의 기본과 고급 기능, 그리고 커스텀 Hooks 까지 활용하면 더욱 효율적인 React 애플리케이션을 개발할 수 있을 것입니다.

반응형