본문 바로가기

React

React Hooks 톺아보기 2 - 고급 Hooks 활용법

반응형

React의 고급 Hooks인 useReducer, useMemo, useCallback, useRef 등을 심층 분석하고, 이를 활용한 최적화 전략과 실전 예제를 통해 효율적인 React 개발 방법을 소개합니다.

 

기본 Hooks 복습 및 고급 Hooks 소개

지난 글 "React Hooks 톺아보기 1 - 기본 Hooks 이해하기"에서는 React의 기본 Hooks인 useState, useEffect, useContext에 대해 자세히 살펴보았습니다. 이제 더욱 복잡하고 강력한 기능을 제공하는 고급 Hooks인 useReducer, useMemo, useCallback, useRef 등을 통해 React 애플리케이션의 성능과 유지보수성을 한층 더 향상시켜 보겠습니다. 고급 Hooks를 활용하면 복잡한 상태 관리와 성능 최적화를 보다 효과적으로 구현할 수 있습니다.

 

고급 Hooks 이해하기

useReducer

useReducer는 복잡한 상태 로직을 관리할 때 유용한 Hook입니다. 특히 상태 전환 로직이 복잡하거나 여러 하위 값이 있는 경우에 적합합니다. useReducer는 상태와 디스패치 함수를 반환하며, 리듀서 함수는 현재 상태와 액션을 받아 새로운 상태를 반환합니다.

사용법

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
	switch (action.type) {
		case '증가':
			return { count: state.count + 1 };
		case '감소':
			return { count: state.count - 1 };
		default:
			throw new Error('알 수 없는 액션 타입');
	}
}

function Counter() {
	const [state, dispatch] = useReducer(reducer, initialState);
    
	return (
		<div>
			<p>현재 카운트: {state.count}</p>
			<button onClick={() => dispatch({ type: '증가' })}>증가</button>
			<button onClick={() => dispatch({ type: '감소' })}>감소</button>
		</div>
	);
}

export default Counter;

 

설명

  • initialState: 상태의 초기값을 정의합니다.
  • reducer: 상태 전환 로직을 담당하는 함수로, 현재 상태와 액션을 받아 새로운 상태를 반환합니다.
  • dispatch: 액션을 디스패치하여 리듀서 함수를 통해 상태를 업데이트합니다.

useMemo와 useCallback

useMemo와 useCallback은 성능 최적화를 위해 사용되는 Hooks입니다. useMemo는 계산 비용이 높은 함수의 결과를 메모이제이션하여 불필요한 재계산을 방지하고, useCallback은 함수를 메모이제이션하여 불필요한 함수 재생성을 방지합니다.

useMemo

import React, { useState, useMemo } from 'react';

function ExpensiveCalculation({ number }) {
	const computeFactorial = (n) => {
		console.log('팩토리얼 계산 중...');
		return n <= 0 ? 1 : n * computeFactorial(n - 1);
	};

	const factorial = useMemo(() => computeFactorial(number), [number]);
    
	return <div>{number}의 팩토리얼: {factorial}</div>;
}

function App() {
	const [number, setNumber] = useState(5);
	const [text, setText] = useState('');
    
	return (
		<div>
			<ExpensiveCalculation number={number} />
			<button onClick={() => setNumber(number + 1)}>숫자 증가</button>
			<input value={text} onChange={(e) => setText(e.target.value)} placeholder="텍스트 입력" />
		</div>
	);
}

export default App;

 

useCallback

import React, { useState, useCallback } from 'react';

function Child({ onClick }) {
	console.log('Child 컴포넌트 렌더링');
	return <button onClick={onClick}>자식 버튼</button>;
}

const MemoizedChild = React.memo(Child);

function Parent() {
	const [count, setCount] = useState(0);
	
	const handleClick = useCallback(() => {
		setCount((prevCount) => prevCount + 1);
	}, []);

	return (
		<div>
			<p>카운트: {count}</p>
			<MemoizedChild onClick={handleClick} />
		</div>
	);
}

export default Parent;

 

설명

  • useMemo: computeFactorial 함수의 결과를 메모이제이션하여 number가 변경될 때만 재계산됩니다. 이를 통해 불필요한 계산을 방지할 수 있습니다.
  • useCallback: handleClick 함수를 메모이제이션하여 Parent 컴포넌트가 리렌더링될 때마다 동일한 함수 인스턴스를 유지합니다. MemoizedChild 컴포넌트가 불필요하게 리렌더링되는 것을 방지합니다.

useRef

useRef는 DOM 요소나 mutable한 값을 참조할 때 사용됩니다. 리렌더링을 트리거하지 않으면서 값을 유지할 수 있습니다.

사용법

import React, { useRef } from 'react';

function TextInput() {
	const inputRef = useRef(null);
	
    const focusInput = () => {
		inputRef.current.focus();
	};

	return (
		<div>
			<input ref={inputRef} type="text" placeholder="텍스트 입력" />
			<button onClick={focusInput}>입력 포커스</button>
		</div>
	);
}

export default TextInput;

 

설명

  • useRef: inputRef는 DOM 요소에 대한 참조를 유지하며, focusInput 함수에서 이를 사용하여 입력 필드에 포커스를 설정합니다.
  • 리렌더링 영향 없음: useRef를 사용해도 컴포넌트는 리렌더링되지 않으며, 참조 값이 변경되어도 상태가 유지됩니다.

 

실전 예제: 고급 Hooks 활용하기

예제: 복잡한 폼 관리

고급 Hooks를 활용하여 복잡한 폼 상태를 관리하는 예제를 만들어보겠습니다. useReducer를 사용해 폼의 상태를 관리하고, useCallback과 useMemo를 통해 성능을 최적화합니다.

FormReducer 설정

// formReducer.js
export const initialFormState = {
	username: '',
	email: '',
	password: '',
};

export function formReducer(state, action) {
	switch (action.type) {
		case 'UPDATE_FIELD':
			return { ...state, [action.field]: action.value };
		case 'RESET':
			return initialFormState;
		default:
			throw new Error('알 수 없는 액션 타입');
	}
}

 

Form 컴포넌트

import React, { useReducer, useCallback } from 'react';
import { formReducer, initialFormState } from './formReducer';

function Form() {
	const [state, dispatch] = useReducer(formReducer, initialFormState);

	const handleChange = useCallback((e) => {
		const { name, value } = e.target;
		dispatch({ type: 'UPDATE_FIELD', field: name, value });
	}, []);
    
	const handleSubmit = useCallback((e) => {
		e.preventDefault();
		console.log('폼 데이터:', state);
		dispatch({ type: 'RESET' });
	}, [state]);
    
	const isFormValid = useMemo(() => {
		return state.username && state.email && state.password;
	}, [state]);
    
	return (
		<form onSubmit={handleSubmit}>
			<div>
				<label>사용자 이름:</label>
				<input
					type="text"
					name="username"
					value={state.username}
					onChange={handleChange}
					required
				/>
			</div>
			<div>
				<label>이메일:</label>
				<input
					type="email"
					name="email"
					value={state.email}
					onChange={handleChange}
					required
				/>
			</div>
			<div>
				<label>비밀번호:</label>
				<input
					type="password"
					name="password"
					value={state.password}
					onChange={handleChange}
					required
				/>
			</div>
			<button type="submit" disabled={!isFormValid}>
				제출
			</button>
		</form>
	);
}

export default Form;

 

설명

  • useReducer: 폼의 상태를 formReducer를 통해 관리하여 상태 전환 로직을 명확하게 분리합니다.
  • useCallback: handleChange와 handleSubmit 함수를 메모이제이션하여 컴포넌트의 불필요한 리렌더링을 방지합니다.
  • useMemo: isFormValid 값을 메모이제이션하여 폼의 유효성을 효율적으로 계산합니다.

 

React Hooks 최적화 전략

고급 Hooks를 효과적으로 활용하기 위해 다음과 같은 최적화 전략을 고려해야 합니다:

의존성 배열 철저 관리

useEffect, useCallback, useMemo 등의 Hooks에서 의존성 배열을 정확히 설정하여 불필요한 재실행을 방지합니다. 모든 외부 변수를 의존성 배열에 포함시키는 것이 중요합니다.

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

 

메모이제이션의 적절한 사용

useMemo와 useCallback을 과도하게 사용하면 오히려 성능 저하를 초래할 수 있습니다. 실제로 성능 최적화가 필요한 부분에만 메모이제이션을 적용하는 것이 좋습니다.

컴포넌트 분리

복잡한 컴포넌트를 작은 단위로 분리하여 관리하면, 각 컴포넌트의 리렌더링을 효과적으로 제어할 수 있습니다. React.memo를 활용하여 불필요한 리렌더링을 방지할 수 있습니다.

import React, { memo } from 'react';

const MemoizedComponent = memo(function({ value }) {
	return <div>{value}</div>;
});

 

마무리하며

이번 글에서는 React의 고급 Hooks인 useReducer, useMemo, useCallback, useRef 등을 심층적으로 살펴보고, 이를 활용한 성능 최적화 전략과 실전 예제를 통해 고급 Hooks의 효과적인 사용법을 배웠습니다. 고급 Hooks를 적절히 활용하면 복잡한 상태 관리와 성능 최적화를 보다 효율적으로 구현할 수 있습니다. 다음 글 "React Hooks 톺아보기 3 - 커스텀 Hooks 작성하기"에서는 커스텀 Hooks를 작성하는 방법과 그 활용 사례에 대해 자세히 다룰 예정입니다.

반응형