모던 리액트 딥 다이브 3 - React Hook
리액트의 모든 훅 파헤치기
3.1 리액트의 모든 훅 파헤치기
3.1.1 useState
useState는 함수형 컴포넌트에서 상태를 관리할 수 있게 해주는 훅입니다. 함수형 컴포넌트 내에서 상태를 선언하고, 해당 상태를 업데이트하는 함수를 반환합니다. useState를 통해 컴포넌트가 재렌더링될 때마다 상태가 유지되도록 할 수 있습니다.
예시 코드
import React, { useState } from 'react';
function Counter() {
// count 상태 변수와 상태를 갱신할 수 있는 setCount 함수를 선언합니다.
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Counter;
설명
위 코드에서 useState(0)는 count라는 상태 변수와 setCount라는 상태 갱신 함수를 반환합니다. count의 초기값은 0이며, setCount를 호출할 때마다 count가 업데이트되고 컴포넌트는 재렌더링됩니다. 버튼을 클릭하면 setCount(count + 1)이 호출되어 count가 1 증가합니다.
3.1.2 useEffect
useEffect는 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정할 때 사용합니다. 주로 데이터 fetching, 구독 설정, 타이머 설정 등의 부수 효과(side effects)를 처리합니다. useEffect는 컴포넌트가 마운트된 직후와 업데이트된 직후에 호출됩니다.
예시 코드
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 컴포넌트가 렌더링될 때마다 실행되는 effect를 설정합니다.
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
설명
위 코드에서 useEffect는 컴포넌트가 렌더링될 때마다 실행됩니다. useEffect 내부의 함수는 count 값이 변경될 때마다 호출되며, 해당 함수는 document.title을 업데이트합니다. 이는 사용자가 버튼을 클릭할 때마다 페이지 제목이 클릭 횟수로 변경되는 것을 의미합니다.
3.1.3 useMemo
useMemo는 성능 최적화를 위해 사용되며, 메모이제이션된 값을 반환합니다. 특정 값이 변경될 때만 계산을 수행하여 불필요한 계산을 피할 수 있습니다. 주로 비용이 많이 드는 계산을 메모이제이션할 때 유용합니다.
예시 코드
import React, { useState, useMemo } from 'react';
function Example() {
const [count, setCount] = useState(0);
const expensiveCalculation = useMemo(() => {
// 비용이 많이 드는 계산 로직
return count * 2;
}, [count]);
return (
<div>
<p>Calculated value: {expensiveCalculation}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Example;
설명
위 코드에서 useMemo는 count가 변경될 때만 재계산을 수행하여 expensiveCalculation 값을 반환합니다. 이는 count가 변경되지 않는 한, 동일한 expensiveCalculation 값을 재사용하여 불필요한 계산을 방지합니다.
3.1.4 useCallback
useCallback은 메모이제이션된 콜백 함수를 반환합니다. 주로 함수형 컴포넌트의 성능을 최적화할 때 사용되며, 동일한 함수를 재사용하여 불필요한 함수 재생성을 방지합니다.
예시 코드
import React, { useState, useCallback } from 'react';
function Example() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
export default Example;
설명
위 코드에서 useCallback은 count가 변경될 때만 handleClick 함수를 재생성합니다. count가 변경되지 않는 한 동일한 handleClick 함수를 재사용하여 컴포넌트의 성능을 최적화합니다.
3.1.5 useRef
useRef는 변경 가능한 ref 객체를 생성하는 데 사용됩니다. 주로 DOM 요소에 접근하거나, 컴포넌트의 인스턴스 메서드에 접근할 때 사용됩니다.
예시 코드
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<div>
<input ref={inputEl} type='text' />
<button onClick={onButtonClick}>Focus the input</button>
</div>
);
}
export default TextInputWithFocusButton;
설명
위 코드에서 useRef는 inputEl이라는 ref 객체를 생성합니다. 버튼을 클릭하면 inputEl.current.focus()가 호출되어 입력 필드에 포커스가 설정됩니다.
3.1.6 useContext
useContext는 컨텍스트의 값을 구독하는 데 사용됩니다. 트리 전체에 데이터를 전달할 때 유용하며, props drilling을 피할 수 있습니다.
예시 코드
// ThemeContext.js
import React, { createContext, useState } from 'react';
const ThemeContext = createContext();
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
return (
<ThemeContext.Provider value=>
{children}
</ThemeContext.Provider>
);
};
export { ThemeProvider, ThemeContext };
// ThemedButton.js
import React, { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
const ThemedButton = () => {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
<button
style=
onClick={toggleTheme}
>
Toggle Theme
</button>
);
};
export default ThemedButton;
// App.js
import React from 'react';
import { ThemeProvider } from './ThemeContext';
import ThemedButton from './ThemedButton';
const App = () => {
return (
<ThemeProvider>
<div style=>
<h1>Themed App</h1>
<ThemedButton />
</div>
</ThemeProvider>
);
};
export default App;
설명
위 코드에서 useContext는 ThemeContext의 값을 구독하여 theme 변수에 저장합니다. ThemedButton 컴포넌트는 theme 값을 사용하여 스타일을 적용합니다.
3.1.7 useReducer
useReducer는 useState의 대안으로, 복잡한 상태 로직을 처리할 때 사용됩니다. 상태가 복잡하고 여러 가지 상태 변경 로직이 필요할 때, 혹은 상태가 관련된 여러 하위 컴포넌트에서 사용될 때 유용합니다.
예시 코드
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
export default Counter;
설명
위 코드에서 useReducer는 상태와 상태를 업데이트하는 dispatch 함수를 반환합니다. reducer 함수는 액션 타입에 따라 상태를 갱신합니다. 버튼을 클릭하면 dispatch 함수가 호출되어 상태가 업데이트됩니다.
3.1.8 useImperativeHandle
useImperativeHandle은 ref를 사용하는 부모 컴포넌트가 자식 컴포넌트의 인스턴스 메서드를 호출할 수 있도록 할 때 사용합니다. 주로 컴포넌트의 내부 메서드에 대한 제어를 부모 컴포넌트에 노출할 필요가 있을 때 사용됩니다.
예시 코드
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
});
export default function App() {
const inputRef = useRef();
return (
<div>
<FancyInput ref={inputRef} />
<button onClick={() => input
Ref.current.focus()}>Focus the input</button>
</div>
);
}
설명
위 코드에서 useImperativeHandle은 부모 컴포넌트가 자식 컴포넌트의 focus 메서드를 호출할 수 있도록 합니다. FancyInput 컴포넌트는 내부적으로 inputRef를 사용하지만, 부모 컴포넌트에서는 inputRef.current.focus()를 호출할 수 있습니다.
3.1.9 useLayoutEffect
useLayoutEffect는 동기적으로 DOM을 업데이트합니다. DOM을 읽고 변경해야 하는 경우 사용됩니다. 이는 브라우저가 화면을 그리기 전에 실행되므로, DOM 변경 후 바로 적용해야 하는 경우에 유용합니다.
예시 코드
import React, { useLayoutEffect, useRef } from 'react';
function LayoutEffectExample() {
const inputRef = useRef(null);
useLayoutEffect(() => {
console.log(inputRef.current.getBoundingClientRect());
}, []);
return <input ref={inputRef} />;
}
export default LayoutEffectExample;
설명
위 코드에서 useLayoutEffect는 DOM 요소의 크기와 위치를 동기적으로 읽습니다. 이는 브라우저가 화면을 그리기 전에 실행되므로, DOM을 읽고 변경해야 하는 경우에 유용합니다.
3.1.10 useDebugValue
useDebugValue는 주로 커스텀 훅에서 디버그 값을 설정하는 데 사용됩니다. 개발자 도구에서 커스텀 훅의 상태를 쉽게 디버깅할 수 있도록 도와줍니다.
예시 코드
import React, { useState, useDebugValue } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
useDebugValue(isOnline ? 'Online' : 'Offline');
// ...상태 업데이트 로직
return isOnline;
}
설명
위 코드에서 useDebugValue는 isOnline 상태를 디버깅하기 위해 사용됩니다. 개발자 도구에서 커스텀 훅의 상태를 ‘Online’ 또는 ‘Offline’으로 표시합니다.
3.1.11 훅의 규칙
- 최상위에서만 호출: 훅은 함수형 컴포넌트의 최상위에서만 호출해야 합니다. 루프, 조건문, 중첩 함수 내에서 훅을 호출해서는 안 됩니다.
- 리액트 함수 컴포넌트 또는 커스텀 훅에서만 호출: 훅은 리액트 함수 컴포넌트 또는 커스텀 훅에서만 호출해야 합니다. 일반 자바스크립트 함수에서는 훅을 호출할 수 없습니다.
3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
3.2.1 사용자 정의 훅
사용자 정의 훅은 여러 컴포넌트에서 재사용할 수 있는 훅입니다. 공통된 로직을 추출하여 재사용할 수 있습니다.
예시 코드
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
});
}, [url]);
return { data, loading };
}
설명
위 코드에서 useFetch 훅은 데이터를 가져오는 로직을 캡슐화하여 여러 컴포넌트에서 재사용할 수 있습니다. URL을 인수로 받아 데이터를 fetch하고, 데이터를 반환합니다.
3.2.2 고차 컴포넌트
고차 컴포넌트(HOC)는 컴포넌트를 인수로 받아들여 새 컴포넌트를 반환하는 함수입니다. UI 로직을 재사용할 때 유용합니다.
예시 코드
import React from 'react';
function withLoading(Component) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return <p>Loading...</p>;
};
}
설명
위 코드에서 withLoading HOC는 isLoading prop에 따라 로딩 상태를 표시하거나 실제 컴포넌트를 렌더링합니다. 이는 로딩 상태 처리를 여러 컴포넌트에서 재사용할 수 있도록 합니다.
3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
사용자 정의 훅과 고차 컴포넌트 중 무엇을 사용할지는 상황에 따라 다릅니다. 상태와 로직을 공유하려면 사용자 정의 훅을, UI를 공유하려면 고차 컴포넌트를 사용하는 것이 일반적입니다.
Leave a comment