1편과 이어집니다!
https://duektmf34.tistory.com/199
이번에 다뤄볼 건
- useCallback
- useMemo
- useRef
- useImperativeHandle
- useLayoutEffect
- useDebugValue
아마 마지막 3개는 간단하게 설명하고 마무리할 것 같다.
useCallback
useCallback Hook은 함수를 기억하고 새로 렌더링될 때마다 함수를 재생성하는 것을 방지하기 위해 사용됩니다.
주로 자식 컴포넌트에 콜백 함수를 props로 전달할 때 사용됩니다.
import React, { useState, useCallback } from 'react';
// 자식 컴포넌트
const ChildComponent = React.memo(({ onClick }) => {
console.log('ChildComponent 렌더링됨');
return <button onClick={onClick}>Click Me</button>;
});
// 부모 컴포넌트
const ParentComponent = () => {
const [count, setCount] = useState(0);
// useCallback을 사용하여 콜백 함수 생성
const handleClick = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
return (
<div>
<h1>Count: {count}</h1>
{/* 자식 컴포넌트에 콜백 함수 전달 */}
<ChildComponent onClick={handleClick} />
</div>
);
};
export default ParentComponent;
이 예제에서는 ParentComponent가 count 상태를 관리하고, 해당 상태를 클릭 이벤트를 통해 증가시키는 handleClick 함수를 정의합니다. useCallback을 사용하여 handleClick 함수를 생성하고, 빈 배열을 의존성 배열로 전달하여 함수의 재생성을 최소화합니다.
💥 useCallback 주의점
- 컴포넌트 분할: useCallback을 사용하여 생성된 함수가 자식 컴포넌트에 전달될 때, 자식 컴포넌트가 불필요하게 재렌더링되는 경우가 있을 수 있습니다. 이를 방지하기 위해 필요한 경우 자식 컴포넌트를 분할하여 최적화할 수 있습니다.
- 너무 많은 함수 생성 방지: useCallback을 사용하여 함수를 생성하는 것이 항상 좋은 것은 아닙니다. 너무 많은 함수 생성은 메모리를 낭비할 수 있으며, 성능에 영향을 줄 수 있습니다. 따라서 useCallback을 사용할 때는 실제로 함수를 재사용해야 하는 경우에만 사용하는 것이 좋습니다.
useMemo
useMemo는 계산 비용이 높은 연산의 결과를 메모이제이션하여 성능을 최적화하는 데 사용됩니다.
주로 렌더링 시에 계산이 많이 필요한 경우나, 불필요한 재계산을 방지할 때 사용됩니다.
useMemo는 클린코드 리액트 강의를 들으면서 높은 연산이 렌더링 될 때 주로 사용한다고 배웠다!
import React, { useState, useMemo } from 'react';
const ExpensiveCalculation = ({ a, b }) => {
// 계산 비용이 높은 연산을 수행하는 함수
const calculateSum = (a, b) => {
console.log('계산 수행됨');
return a + b;
};
// useMemo를 사용하여 결과값을 메모이제이션
const sum = useMemo(() => calculateSum(a, b), [a, b]);
return <p>Sum of {a} and {b} is {sum}</p>;
};
const App = () => {
const [valueA, setValueA] = useState(0);
const [valueB, setValueB] = useState(0);
return (
<div>
<input
type="number"
value={valueA}
onChange={e => setValueA(Number(e.target.value))}
/>
<input
type="number"
value={valueB}
onChange={e => setValueB(Number(e.target.value))}
/>
<ExpensiveCalculation a={valueA} b={valueB} />
</div>
);
};
export default App;
이 예제에서는 부모 컴포넌트인 App 컴포넌트에서 두 개의 입력 값을 관리하고 있습니다. 입력 값이 변경될 때마다 ExpensiveCalculation 컴포넌트가 렌더링되고, 해당 컴포넌트에서는 두 수의 합을 계산합니다.
calculateSum 함수는 계산 비용이 높은 연산을 수행하며, 이를 useMemo를 사용하여 메모이제이션합니다. 두 번째 매개변수로 전달된 [a, b] 배열은 해당 값을 의존성으로 지정하여, 입력 값이 변경될 때에만 calculateSum 함수가 실행되도록 설정합니다.
이렇게 함으로써 입력 값이 변경되지 않는 한, 같은 결과를 가진 calculateSum 함수가 재실행되지 않고 이전에 계산된 결과를 재사용합니다.
💥 useMemo 주의점
- 너무 복잡한 로직: useMemo를 사용하여 복잡한 로직을 최적화하려는 경우, 코드의 가독성과 유지보수성이 저하될 수 있습니다. 따라서 최적화가 실제로 필요한지 여부를 고려하고, 필요한 경우에만 useMemo를 사용해야 합니다.
- 너무 많은 메모이제이션: 모든 값에 대해 useMemo를 사용하여 메모이제이션하는 것은 권장되지 않습니다. 필요한 경우에만 메모이제이션하여 성능을 최적화해야 합니다.
useRef
useRef 는 함수 컴포넌트에서 DOM 요소나 다른 값들을 참조할 수 있게 해주는 기능을 제공합니다. 주로 DOM 요소에 직접 접근해야 하는 경우나 컴포넌트 간의 통신에 사용됩니다.
import React, { useState, useRef } from 'react';
const CounterComponent = () => {
const [count, setCount] = useState(0);
const prevCountRef = useRef();
// 이전 상태를 useRef를 사용하여 저장
useEffect(() => {
prevCountRef.current = count;
}, [count]);
const handleIncrement = () => {
setCount(prevCount => prevCount + 1);
};
const handleDecrement = () => {
setCount(prevCount => prevCount - 1);
};
return (
<div>
<h2>Count: {count}</h2>
<h3>Previous Count: {prevCountRef.current}</h3>
<button onClick={handleIncrement}>증가</button>
<button onClick={handleDecrement}>감소</button>
</div>
);
};
export default CounterComponent;
이 예제에서는 useState를 사용하여 count라는 상태 값을 관리하고 있습니다. 또한 useRef를 사용하여 이전 상태 값을 유지합니다.
useEffect 훅을 사용하여 컴포넌트가 리렌더링될 때마다 prevCountRef.current에 현재 count 값을 할당합니다. 이전 상태를 저장하는 것은 useEffect 내부에서 이루어집니다.
그리고 버튼을 클릭하여 count 값을 변경할 때마다 이전 상태 값을 출력합니다. prevCountRef.current를 통해 이전 상태 값을 가져올 수 있습니다.
이러한 방식으로 useRef를 사용하면 함수 컴포넌트 내에서 이전 상태를 유지할 수 있으며, 이를 통해 이전 상태를 활용하는 다양한 기능을 구현할 수 있습니다.
- 렌더링과 관계 없는 값 저장: useRef를 사용하여 저장된 값은 컴포넌트의 렌더링과 관계 없이 유지됩니다. 따라서 렌더링에 영향을 주지 않는 값을 저장하는 데에만 사용해야 합니다.
- DOM 요소 접근 시 주의: useRef를 사용하여 DOM 요소에 접근할 때, 해당 요소가 실제로 렌더링된 후에 접근해야 합니다. 렌더링 이후에 접근하려면 useEffect Hook 내에서 접근해야 합니다.
- 변경 사항 감지 없음: useRef는 값이 변경될 때 재렌더링되지 않습니다. 따라서 useRef로 관리되는 값이 변경되어도 컴포넌트가 다시 렌더링되지 않습니다.
useImperativeHandle
useImperativeHandle은 함수 컴포넌트에서 생성된 ref 객체의 인스턴스를 조작하고 사용자 정의 메서드를 노출할 때 사용하는 Hook입니다. 이 Hook은 주로 부모 컴포넌트가 자식 컴포넌트의 인스턴스를 직접적으로 조작해야 할 때 사용됩니다.
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
// 자식 컴포넌트
const ChildComponent = forwardRef((props, ref) => {
const internalRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
internalRef.current.focus();
},
// 다른 사용자 정의 메서드들...
}));
return <input ref={internalRef} />;
});
// 부모 컴포넌트
const ParentComponent = () => {
const childRef = useRef();
const handleClick = () => {
// 부모 컴포넌트에서 자식 컴포넌트의 포커스 메서드를 호출
childRef.current.focus();
};
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleClick}>Focus Child Component</button>
</div>
);
};
export default ParentComponent;
useLayoutEffect
useLayoutEffect는 브라우저에 변화가 일어나기 전에 DOM을 조작하거나 측정하기 위해 사용됩니다. useLayoutEffect의 동작은 useEffect와 유사하지만, useEffect와 달리 동기적으로 작동합니다.
import React, { useLayoutEffect, useState } from 'react';
const ExampleComponent = () => {
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
const updateWidth = () => {
const newWidth = document.documentElement.clientWidth;
setWidth(newWidth);
};
// 컴포넌트가 마운트될 때와 브라우저의 크기가 변경될 때마다 width를 업데이트합니다.
window.addEventListener('resize', updateWidth);
updateWidth(); // 컴포넌트가 마운트될 때 초기 width를 설정합니다.
return () => {
window.removeEventListener('resize', updateWidth);
};
}, []); // 의존성 배열이 빈 배열이므로 컴포넌트가 마운트될 때 한 번만 실행됩니다.
return <div>Width of the window: {width}px</div>;
};
export default ExampleComponent;
이 예제에서는 useLayoutEffect를 사용하여 브라우저의 창 크기가 변경될 때마다 창의 폭을 측정하고 상태로 업데이트합니다. 이 때 useLayoutEffect를 사용하는 이유는 브라우저 크기를 측정하는 DOM 조작이 렌더링 전에 발생해야 하기 때문입니다.
useDebugValue
useDebugValue는 디버깅을 위해 컴포넌트의 현재 상태나 프롭스 값을 DevTools에서 더 쉽게 확인할 수 있도록 도와줍니다.
일반적으로 props 값은 개발자 도구의 컴포넌트 트리에서 확인할 수 있지만 이 훅을 쓰면 더 읽기 쉽게 표시할 수 있단 말씀!!!
import React, { useState, useDebugValue } from 'react';
const useCustomHook = (initialValue) => {
const [value, setValue] = useState(initialValue);
// 값이 홀수인지 짝수인지에 따라 라벨을 지정하여 DevTools에 표시합니다.
useDebugValue(value % 2 === 0 ? '짝수' : '홀수');
return [value, setValue];
};
const ExampleComponent = () => {
const [count, setCount] = useCustomHook(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
};
export default ExampleComponent;
이 예시에서는 useCustomHook이라는 커스텀 훅을 정의하고 있습니다. 이 훅은 현재 상태 값과 이 값을 변경하는 메서드를 반환합니다. useDebugValue를 사용하여 현재 값이 홀수인지 짝수인지를 확인하고 해당 값을 DevTools에 표시합니다.
디버깅에 대한 정확성을 필요로 할 때 쓰면 좋은 훅인 것 같다.
오늘은 이렇게 리액트 훅 종류에 대해 전반적으로 정리를 해봤는데 개념을 알고 예제까지 살펴보니 더 명확하게 알겠다.
잘 기억해놨다가 프로젝트에서 유용하게 써먹어야겠다!