React의 useRef
와 useState
는 둘 다 컴포넌트 상태를 관리하기 위한 훅이지만, 사용하는 목적과 리액트 라이프사이클에서의 동작 방식이 다릅니다.
useRef
useRef
는 DOM 요소나 컴포넌트 인스턴스에 대한 참조를 유지하거나, 값이 컴포넌트의 렌더링과 상관없이 유지되어야 할 때 사용합니다.useRef
로 생성된 객체는 컴포넌트가 다시 렌더링되어도 동일한 객체를 유지합니다. current
속성의 값이 바뀌어도, 컴포넌트를 다시 렌더링하지 않습니다.import { useRef, useEffect } from 'react';
function MyComponent() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
}
useState
useState
는 컴포넌트의 상태를 관리하기 위해 사용됩니다. 상태가 변하면 해당 상태를 사용하는 컴포넌트가 다시 렌더링됩니다.useState
로 설정된 상태 값이 변경되면, 리액트는 해당 상태를 사용하고 있는 컴포넌트를 다시 렌더링합니다.import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<p>{count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
useState
는 상태가 변경되면 컴포넌트를 다시 렌더링합니다.useRef
는 current
속성이 변경되어도 컴포넌트를 다시 렌더링하지 않습니다.useState
는 컴포넌트의 상태를 관리합니다.useRef
는 DOM 요소에 대한 참조 또는 렌더링과 상관없이 값을 유지하는 데 사용됩니다.이렇게 각 훅의 특성을 이해하고 상황에 맞게 사용하는 것이 중요합니다.
useEffect
useEffect
는 React Hook 중 하나로, 함수형 컴포넌트에서 부수 효과(side effect)를 수행하기 위해 사용됩니다. 예를 들어, 데이터 fetching, 구독 설정 및 해제, DOM 조작 등이 포함됩니다. useEffect
의 파라미터는 두 가지 있습니다:
useEffect(effectFunction, dependencies);
각 파라미터에 대해 자세히 설명하겠습니다.
effectFunction (필수)
effectFunction
내부에서는 데이터를 fetch하거나, 이벤트 리스너를 추가하는 등의 작업을 할 수 있습니다.예시:
useEffect(() => {
// effect code, e.g., fetching data
return () => {
// clean-up code, e.g., removing event listeners
};
}, []);
dependencies (선택)
effectFunction
내에서 사용되는 값들을 포함하는 배열입니다.effectFunction
이 다시 호출됩니다.effectFunction
은 컴포넌트가 처음 렌더링될 때 한 번만 실행됩니다.useEffect
는 컴포넌트가 렌더링될 때마다, 즉 매번 컴포넌트가 업데이트될 때마다 실행됩니다. 이 경우는 의존성 배열에 특정 값을 넣는 것이 의미가 없거나, 모든 렌더링마다 부수 효과가 필요할 때 사용됩니다.useEffect
가 실행되어 항상 최신 상태를 반영할 수 있습니다. 그러나 필요 없이 빈번하게 실행되지 않도록 주의해야 합니다.예시:
useEffect(() => {
// effect code
}, [dependency1, dependency2]);
예제 1: 빈 의존성 배열 (한 번만 실행)
useEffect(() => {
console.log('Component mounted');
}, []);
예제 2: 의존성 배열 포함 (의존성이 변경될 때마다 실행)
useEffect(() => {
console.log('Dependency changed:', someDependency);
}, [someDependency]);
예제 3: 클린업 함수 포함
useEffect(() => {
const handleResize = () => {
console.log('Window resized');
};
window.addEventListener('resize', handleResize);
// Cleanup function
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
위 예시들을 참고하여 다양한 방식으로 useEffect
를 사용할 수 있습니다. 필요에 따라 적절한 의존성 배열을 설정하는 것이 중요합니다.
useImperativeHandle
훅은 부모 컴포넌트가 자식 컴포넌트 인스턴스에 접근할 수 있는 방법을 제공하여, 커스텀 메서드를 사용할 수 있도록 합니다. 주로 포워딩된 참조(Ref)를 사용하는 경우에 유용합니다. React.forwardRef
와 함께 사용하여 구현합니다.
아래는 useImperativeHandle
의 사용 예시입니다.
때로는 부모 컴포넌트가 자식 컴포넌트의 특정 기능에 대해 직접 접근해서 호출해야 할 때가 있습니다. 이런 경우 useImperativeHandle
을 사용하면 됩니다.
import React, { useImperativeHandle, forwardRef, useRef } from 'react';
// forwardRef를 사용하여 Ref 전달을 가능하게 함
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
// useImperativeHandle을 사용하여 부모가 사용할 수 있는 메서드를 정의
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} type="text" />;
});
export default CustomInput;
import React, { useRef } from 'react';
import CustomInput from './CustomInput';
const ParentComponent = () => {
const inputRef = useRef();
const handleFocus = () => {
inputRef.current.focus();
};
const handleClear = () => {
inputRef.current.clear();
};
return (
<div>
<CustomInput ref={inputRef} />
<button onClick={handleFocus}>Focus Input</button>
<button onClick={handleClear}>Clear Input</button>
</div>
);
};
export default ParentComponent;
CustomInput 컴포넌트:
forwardRef
를 사용하여 부모 컴포넌트로부터 전달된 ref
를 받을 수 있게 합니다.useImperativeHandle
훅을 사용하여 부모가 사용할 수 있는 커스텀 메서드를 정의합니다. 이 메서드들은 ref
객체의 현재 값을 설정하는 함수들입니다.inputRef
는 실제 DOM 요소에 대한 참조를 갖습니다.ParentComponent 컴포넌트:
useRef
를 사용하여 inputRef
참조를 생성합니다.inputRef.current
를 통해 CustomInput
컴포넌트에서 정의한 커스텀 메서드 focus
와 clear
를 호출합니다.위와 같은 방법으로 useImperativeHandle
을 사용하여 부모 컴포넌트가 자식 컴포넌트의 특정 기능에 접근할 수 있게 할 수 있습니다. 이는 특히 여러 자식 컴포넌트를 다루거나 특정 조건에서만 자식 컴포넌트의 메서드를 호출해야 하는 경우에 유용합니다.
useReducer
훅은 리액트에서 상태 관리를 더 구조화하고 복잡한 상태 로직을 다룰 때 유용합니다. 주로 상태가 여러 가지로 나뉘어지고 변화가 복잡한 경우에 사용하는데, 이는 redux
와 비슷한 패턴을 따릅니다.
아래는 useReducer
를 사용한 상태 관리의 예시입니다.
useReducer
는 기본적으로 두 가지 인자를 가지며, 상태와 이를 업데이트하는 dispatch
함수를 반환합니다.
const [state, dispatch] = useReducer(reducer, initialState);
reducer
는 상태를 업데이트하는 함수입니다.initialState
는 초기 상태 값입니다.리듀서 함수는 현재 상태와 액션을 받아서 새로운 상태를 반환합니다.
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 };
case 'reset':
return { count: 0 };
default:
throw new Error();
}
}
useReducer
훅을 사용하여 상태를 관리하는 컴포넌트를 만듭니다.
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 };
case 'reset':
return { count: 0 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
<button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
export default Counter;
리듀서 함수 정의:
reducer
함수는 두 가지 인자를 받습니다:state
: 현재 상태action
: 상태를 변화시킬 명령을 내리는 객체switch
문을 통해 다양한 액션 타입에 따라 상태를 업데이트합니다.초기 상태 정의:
initialState
는 초기 값으로 { count: 0 }
을 가집니다.Counter 컴포넌트:
useReducer
훅을 사용하여 state
와 dispatch
함수를 가져옵니다.dispatch
함수는 액션 객체를 받아 reducer
함수에 전달하여 상태를 업데이트합니다.dispatch
에 전달합니다.이렇게 하면 상태 변화 로직이 각각의 컴포넌트에서 분리되어 보다 명확하고 관리하기 쉬워집니다. useReducer
는 상태 로직이 복잡하고 다양한 상태를 처리해야 하는 경우에 특히 유용합니다.
useLayoutEffect
는 useEffect
와 유사하지만, DOM 변화 직후 그리고 브라우저가 그 내용을 스크린에 그리기 전에 실행됩니다. 이 훅은 렌더링 성능에 민감한 작업을 수행해야 할 때 주로 사용됩니다. 예를 들면, 레이아웃을 측정하거나 동기적으로 DOM을 업데이트하는 경우에 적합합니다.
useEffect
: 비동기적으로 실행됨 (브라우저 페인트 후 실행)useLayoutEffect
: 동기적으로 실행됨 (브라우저 페인트 전에 실행)아래 예시는 모든 div
요소의 높이를 측정하고 그 높이에 기반하여 UI를 업데이트하는 예시입니다.
import React, { useLayoutEffect, useRef, useState } from 'react';
function MeasureExample() {
const divRef = useRef(null);
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
if (divRef.current) {
setHeight(divRef.current.getBoundingClientRect().height);
}
}, []);
return (
<div>
<div ref={divRef} style={{ padding: '20px', border: '1px solid black' }}>
Hello, I am the content inside the div. Resize me to see effect!
</div>
<p>The above div's height is: {height}px</p>
</div>
);
}
export default MeasureExample;
useRef로 DOM 접근:
divRef
를 생성하여 div
요소에 대한 참조를 저장합니다.ref
속성을 div
에 설정하여 해당 DOM 노드에 직접 접근할 수 있게 합니다.useState로 상태 관리:
height
라는 상태를 만들어 div
요소의 높이를 저장합니다.useLayoutEffect로 높이 측정:
useLayoutEffect
가 실행됩니다.divRef.current.getBoundingClientRect().height
를 호출하여 div
의 높이를 측정하고, 이 값을 height
상태에 저장합니다.height
상태가 업데이트되면 컴포넌트는 다시 렌더링되고, 높이가 화면에 표시됩니다.이 예시를 통해 useLayoutEffect
가 브라우저가 내용을 그리기 전에 실행되어 DOM 측정 및 동기화에 유용함을 알 수 있습니다. 이를 활용하면 사용자 경험을 향상시키고 컨텐츠 깜빡임을 방지할 수 있습니다.
useCallback
훅은 메모이제이션된 콜백 함수를 생성하는 데 사용됩니다. 이는 리액트가 매 렌더링 시마다 새로운 함수 객체를 생성하는 것을 방지하여 성능 최적화를 도와줍니다. 특히, 하위 컴포넌트에 콜백 함수를 props로 전달할 때 유용합니다.
메모이제이션이란 값이 변경되지 않으면 이전에 계산된 결과를 재사용하여 불필요한 연산을 줄이는 방법입니다.
useCallback
훅은 두 개의 인자를 가집니다.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
리스트 항목을 렌더링하고, 항목 클릭 이벤트를 처리하는 예제를 통해 useCallback
의 사용법을 설명하겠습니다.
import React, { useCallback, useState } from 'react';
import List from './List';
function App() {
const [count, setCount] = useState(0);
const [items, setItems] = useState([1, 2, 3, 4, 5]);
const handleItemClick = useCallback((item) => {
console.log(`Item clicked: ${item}`);
}, [items]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment</button>
<p>Count: {count}</p>
<List items={items} onItemClick={handleItemClick} />
</div>
);
}
export default App;
import React from 'react';
function List({ items, onItemClick }) {
return (
<ul>
{items.map(item => (
<li key={item} onClick={() => onItemClick(item)}>
{item}
</li>
))}
</ul>
);
}
export default List;
상태 설정:
count
와 items
상태를 useState
훅을 사용하여 설정합니다.handleItemClick
콜백 함수를 정의합니다. 이 함수는 목록 항목이 클릭될 때 호출됩니다.useCallback 사용:
handleItemClick
은 useCallback
훅을 사용하여 메모이제이션됩니다. 이는 items
배열이 변경되지 않는 한 매 렌더링마다 동일한 함수가 유지됨을 의미합니다.[items]
은 items
배열이 변경될 때만 handleItemClick
함수가 다시 생성되도록 합니다.자식 컴포넌트 및 prop 전달:
List
에 items
와 handleItemClick
을 props로 전달합니다.List
컴포넌트는 리스트의 각 항목을 렌더링하며, 항목이 클릭되면 handleItemClick
콜백을 호출합니다.이 예제에서 useCallback
을 사용하지 않는다면, App
컴포넌트가 렌더될 때마다 새로운 handleItemClick
함수가 생성될 것입니다. 이는 List
컴포넌트가 불필요하게 다시 렌더링되는 결과를 초래할 수 있습니다. useCallback
을 사용함으로써 이러한 불필요한 렌더링을 방지하여 성능을 최적화할 수 있습니다.
useCallback
훅은 메모이제이션된 콜백 함수를 생성하여 함수 객체가 불필요하게 다시 생성되는 것을 방지합니다.이 예제를 통해 리액트에서 useCallback
이 어떻게 성능 최적화에 기여할 수 있는지, 특히 콜백 함수가 종속된 상태나 props가 변경될 때만 다시 생성되도록 하는 방법을 이해할 수 있습니다.
useMemo
훅은 연산 비용이 높은 작업의 결과를 메모이제이션하여 성능 최적화를 돕는 역할을 합니다. 이를 통해 컴포넌트가 불필요하게 재렌더링되는 것을 방지할 수 있습니다. useMemo
는 특정 값이 변경되었을 때만 메모이제이션된 값을 다시 계산합니다.
useMemo
는 두 개의 인자를 받습니다:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
computeExpensiveValue(a, b)
함수는 a
또는 b
가 변경될 때만 다시 실행됩니다.아래 예시는 입력 값에 따라 필터링된 리스트를 계산하고, 이 작업이 불필요하게 다시 실행되지 않도록 하는 예시입니다.
import React, { useState, useMemo } from 'react';
function List({ items }) {
const [filter, setFilter] = useState('');
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.toLowerCase().includes(filter.toLowerCase()));
}, [items, filter]);
return (
<div>
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Filter items"
/>
<ul>
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
export default List;
상태 관리:
filter
상태는 필터 텍스트 입력값을 저장합니다.useMemo 사용:
filteredItems
는 useMemo
훅을 사용하여 계산된 리스트입니다.items
와 filter
가 변경될 때만 필터링 연산이 수행됩니다.useMemo
는 의존성 배열 [items, filter]
을 보고 이 값들이 변경되었을 때만 필터링 함수를 다시 실행합니다.필터링 로직:
items
배열을 filter
문자열에 맞게 필터링합니다.useMemo
를 사용함으로써 items
나 filter
가 변경되지 않으면 필터링 작업을 다시 수행하지 않습니다.useMemo
를 사용하지 않으면, List
컴포넌트가 렌더링될 때마다 items.filter
연산이 다시 실행됩니다. 이는 특히 items
배열이 크거나 필터링 연산이 복잡할 경우 성능에 큰 영향을 줄 수 있습니다. useMemo
를 사용하면 필요할 때만 필터링 연산을 수행하여 성능을 최적화할 수 있습니다.
아래는 숫자 리스트의 합계를 계산하는 예시입니다. 이 역시 사용자 입력에 따라 값이 변경될 때만 다시 계산하도록 useMemo
를 사용합니다.
import React, { useState, useMemo } from 'react';
function SumList() {
const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]);
const [newNumber, setNewNumber] = useState('');
const addNumber = () => {
setNumbers([...numbers, parseInt(newNumber)]);
setNewNumber('');
};
const totalSum = useMemo(() => {
console.log('Calculating sum...');
return numbers.reduce((sum, number) => sum + number, 0);
}, [numbers]);
return (
<div>
<input
type="number"
value={newNumber}
onChange={(e) => setNewNumber(e.target.value)}
placeholder="Add a number"
/>
<button onClick={addNumber}>Add</button>
<p>Sum of numbers: {totalSum}</p>
</div>
);
}
export default SumList;
상태 관리:
numbers
상태는 숫자들의 배열을 저장합니다.newNumber
상태는 입력된 숫자 값을 저장합니다.useMemo 사용:
totalSum
은 useMemo
훅을 사용하여 숫자 배열의 합계를 계산합니다.numbers
배열이 변경될 때만 합계를 다시 계산합니다.useMemo
훅은 연산 비용이 높은 작업의 결과를 메모이제이션하여 성능을 최적화합니다.useMemo
를 사용함으로써 불필요한 연산을 피하고, 성능을 향상시킬 수 있습니다.이 예제를 통해 useMemo
가 어떻게 사용되고, 연산 비용이 높은 작업을 최적화하는 데 얼마나 유용한지 이해할 수 있습니다. 이를 적절히 활용하면 리액트 컴포넌트의 성능을 크게 향상시킬 수 있습니다.