React 개발자를 위한 필수 최적화: useCallback 완벽 해부 mymaster, 2024년 06월 16일 React로 복잡한 웹 애플리케이션을 개발하다 보면 컴포넌트의 재렌더링으로 인해 성능 저하를 경험할 수 있습니다. 이는 사용자 경험을 해칠 수 있는 중요한 문제입니다. 다행히 React는 이러한 문제를 해결하기 위한 강력한 도구들을 제공하며, 그 중 하나가 바로 useCallback 훅입니다. 이 글에서는 useCallback 훅이 무엇이고, 왜 중요하며, 어떻게 사용하는지 자세히 알아보겠습니다. 초보자도 쉽게 이해할 수 있도록 예제와 함께 설명할 테니, 이 글을 끝까지 읽으면 useCallback을 이용하여 React 애플리케이션의 성능을 향상하는 방법을 완벽하게 이해할 수 있을 것입니다. 1. useCallback이란 무엇인가요? useCallback은 React에서 제공하는 훅 중 하나로, 컴포넌트가 재렌더링될 때마다 함수를 새로 생성하는 것을 방지하고 이전 렌더링에서 사용했던 함수를 재사용할 수 있도록 도와줍니다. 자, 이게 무슨 의미일까요? 좀 더 쉽게 이해하기 위해 예를 들어 설명해보겠습니다. 친구와 약속을 잡았다고 가정해봅시다. 친구에게 “내일 저녁 7시에 강남역 3번 출구 앞에서 보자” 라고 메시지를 보냈습니다. (함수를 생성했습니다!) 잠시 후, 친구에게 다시 “내일 저녁 7시에 강남역 3번 출구 앞에서 보자” 라고 메시지를 보냈습니다. (같은 내용의 함수를 또 생성했습니다!) 두 메시지 내용은 사실상 동일합니다. 하지만 컴퓨터는 이 둘을 완전히 다른 메시지로 인식합니다. 마치 두 번째 메시지에서 글씨체를 바꾼 것처럼, 컴퓨터는 아주 작은 변화라도 감지하고 이전 정보를 버리고 완전히 새로운 정보로 처리하기 때문입니다. useCallback은 이런 상황을 방지하는 역할을 합니다. 즉, 두 번째 메시지를 보낼 때 “이미 같은 내용의 메시지를 보냈으니까 이전 메시지를 참고해!” 라고 컴퓨터에게 알려주는 것과 같습니다. 2. 왜 useCallback을 사용해야 할까요? useCallback은 React 애플리케이션의 성능 최적화에 매우 중요한 역할을 합니다. useCallback을 사용하면 다음과 같은 이점을 얻을 수 있습니다. 2.1 불필요한 재렌더링 방지 React 컴포넌트는 상태(state)가 변경되거나 props가 업데이트될 때마다 재렌더링됩니다. 만약 컴포넌트 내부에서 함수를 선언하고 이를 자식 컴포넌트에 props로 전달하는 경우, 부모 컴포넌트가 재렌더링될 때마다 새로운 함수가 생성되어 자식 컴포넌트 또한 불필요하게 재렌더링될 수 있습니다. useCallback을 사용하면 이러한 불필요한 재렌더링을 방지하여 애플리케이션의 성능을 향상시킬 수 있습니다. 예를 들어, 아래 코드를 살펴보겠습니다. function ParentComponent() { const [count, setCount] = useState(0); const handleClick = () => { setCount(count + 1); }; return <ChildComponent onClick={handleClick} />; } 위 코드에서 handleClick 함수는 ParentComponent가 렌더링될 때마다 새롭게 생성됩니다. 따라서 ParentComponent가 재렌더링될 때마다 ChildComponent 또한 재렌더링됩니다. 하지만 아래와 같이 useCallback을 사용하면 handleClick 함수는 count 값이 변경될 때만 새롭게 생성됩니다. function ParentComponent() { const [count, setCount] = useState(0); const handleClick = useCallback(() => { setCount(count + 1); }, [count]); return <ChildComponent onClick={handleClick} />; } 2.2 메모이 사용량 감소 useCallback을 사용하면 함수를 메모이제하여 재사용하기 때문에 메모리 사용량을 줄일 수 있습니다. 특히 대규모 애플리케이션이나 복잡한 컴포넌트에서 useCallback을 사용하면 메모리 누수를 방지하고 애플리케이션의 안정성을 높일 수 있습니다. 2.3 복잡한 컴포넌트 최적화 useCallback은 복잡한 컴포넌트를 최적화하는 데 특히 유용합니다. 예를 들어, 아래와 같은 경우 useCallback을 사용하면 유용합니다. 컴포넌트가 자주 재렌더링되는 경우 컴포넌트가 많은 자식 컴포넌트를 가지고 있는 경우 컴포넌트가 복잡한 계산을 수행하는 경우 3. useCallback은 어떻게 사용하나요? useCallback 훅은 다음과 같은 형식으로 사용합니다. const memoizedCallback = useCallback( () => { // 실행할 코드 }, [dependency1, dependency2, ...] ); callback: 메모이제할 함수입니다. dependencies: 의존성 배열입니다. 이 배열에 포함된 값이 변경되면 useCallback은 새로운 함수를 생성합니다. 만약 의존성 배열을 비워두면, 컴포넌트가 처음 렌더링될 때 한 번만 함수를 생성하고 이후에는 재사용합니다. useCallback 훅을 사용하는 방법을 단계별로 자세히 알아보겠습니다. 3.1 useCallback 훅 불러오기 가장 먼저, useCallback 훅을 사용하기 위해 React에서 해당 훅을 불러와야 합니다. import React, { useCallback } from 'react'; 3.2 메모이제할 함수 정의 메모이제할 함수를 정의합니다. 이 함수는 useCallback 훅의 첫 번째 인자로 전달됩니다. const handleChange = useCallback((event) => { setInputValue(event.target.value); }, []); 위 예제에서 handleChange 함수는 입력 필드의 값이 변경될 때마다 호출되어 inputValue 상태를 업데이트합니다. 3.3 의존성 배열 설정 useCallback 훅의 두 번째 인자로 의존성 배열을 전달합니다. 의존성 배열에는 메모이제된 함수가 참조하는 변수 또는 값을 포함해야 합니다. 의존성 배열에 포함된 값 중 하나라도 변경되면 useCallback 훅은 새로운 함수를 생성합니다. const handleChange = useCallback((event) => { setInputValue(event.target.value); }, [setInputValue]); 위 예제에서 의존성 배열에는 setInputValue 함수가 포함되어 있습니다. setInputValue 함수는 useState 훅에 의해 생성되며, 이 함수가 변경되면 handleChange 함수도 새롭게 생성됩니다. 3.4 메모이제된 함수 사용 useCallback 훅을 사용하여 생성한 메모이제된 함수는 일반 함수와 동일하게 사용할 수 있습니다. return ( <div> <input type="text" onChange={handleChange} /> </div> ); 위 예제에서 handleChange 함수는 입력 필드의 onChange 이벤트 핸들러로 사용됩니다. 4. useCallback 사용 시 주의 사항 useCallback은 강력한 도구이지만, 모든 상황에서 무조건 사용해야 하는 것은 아닙니다. useCallback을 남용하면 오히려 코드의 복잡성이 증가하고 성능이 저하될 수 있습니다. 4.1 과도한 사용 자제 useCallback을 모든 함수에 적용하는 것은 불필요합니다. 오히려 코드의 가독성을 떨어뜨리고 성능을 저하시킬 수 있습니다. useCallback은 꼭 필요한 경우에만 사용하는 것이 좋습니다. 4.2 의존성 배열 설정 useCallback 훅을 사용할 때는 의존성 배열을 신중하게 설정해야 합니다. 의존성 배열에 불필요한 값이 포함되면 메모이제된 함수가 제대로 동작하지 않을 수 있습니다. 반대로, 의존성 배열에 필요한 값이 누락되면 불필요한 재렌더링이 발생할 수 있습니다. 4.3 성능 측정 useCallback을 사용하기 전후에 애플리케이션의 성능을 측정하여 실제로 성능이 향상되었는지 확인하는 것이 좋습니다. 성능 측정 도구를 사용하면 useCallback을 사용하여 얻을 수 있는 성능 향상 효과를 정량적으로 파악할 수 있습니다. 5. useCallback과 React.memo의 조합 useCallback은 React.memo와 함께 사용하면 더욱 강력한 성능 최적화 효과를 얻을 수 있습니다. React.memo는 컴포넌트를 메모이제하여 불필요한 재렌더링을 방지하는 고차 컴포넌트(HOC)입니다. 자식 컴포넌트를 React.memo로 감싸고, 해당 컴포넌트에 props로 전달되는 함수를 useCallback으로 감싸면, 부모 컴포넌트가 재렌더링되더라도 자식 컴포넌트는 재렌더링되지 않습니다. const ChildComponent = React.memo(({ onClick }) => { // ... }); function ParentComponent() { // ... const handleClick = useCallback(() => { // ... }, []); return <ChildComponent onClick={handleClick} />; } 6. useCallback 활용 예제 useCallback은 다양한 상황에서 유용하게 활용될 수 있습니다. 몇 가지 예제를 통해 useCallback 활용법을 좀 더 자세히 알아보겠습니다. 6.1 입력 값 처리 입력 필드의 값을 처리하는 경우, useCallback을 사용하여 입력 값이 변경될 때마다 함수가 새롭게 생성되는 것을 방지할 수 있습니다. function InputComponent() { const [inputValue, setInputValue] = useState(''); const handleChange = useCallback((event) => { setInputValue(event.target.value); }, [setInputValue]); return ( <div> <input type="text" value={inputValue} onChange={handleChange} /> </div> ); } 6.2 이벤트 핸들러 이벤트 핸들러를 정의할 때 useCallback을 사용하면 이벤트 객체가 변경될 때마다 함수가 새롭게 생성되는 것을 방지할 수 있습니다. function ButtonComponent() { const handleClick = useCallback((event) => { console.log('Button clicked!', event); }, []); return <button onClick={handleClick}>Click me</button>; } 6.3 목록 렌더링 map 함수를 사용하여 목록을 렌더링할 때 useCallback을 사용하면 각 항목에 대한 함수가 새롭게 생성되는 것을 방지할 수 있습니다. function ListComponent({ items }) { const renderItem = useCallback((item) => { return <li key={item.id}>{item.name}</li>; }, []); return ( <ul> {items.map((item) => renderItem(item))} </ul> ); } 7. 결론 useCallback은 React 애플리케이션의 성능을 향상시키는 데 유용한 도구입니다. 특히 복잡한 애플리케이션이나 컴포넌트에서 useCallback을 적절히 사용하면 불필요한 재렌더링을 방지하고 메모리 사용량을 줄일 수 있습니다. 하지만 useCallback을 남용하면 코드의 복잡성이 증가하고 오히려 성능이 저하될 수 있으므로 주의해야 합니다. useCallback을 사용하기 전에 꼭 필요한 경우인지, 의존성 배열을 올바르게 설정했는지, 성능 측정을 통해 실제로 효과가 있는지 확인하는 것이 좋습니다. useCallback과 React.memo를 함께 사용하면 더욱 강력한 성능 최적화 효과를 얻을 수 있습니다. useCallback을 통해 메모이제된 함수를 생성하고, React.memo를 통해 컴포넌트를 메모이제하면 불필요한 재렌더링을 최소화하여 애플리케이션의 성능을 향상시킬 수 있습니다. React 개발자라면 useCallback을 숙지하고 적절하게 활용하여 고성능 React 애플리케이션을 개발할 수 있도록 노력해야 합니다. 목차 Toggle 1. useCallback이란 무엇인가요?2. 왜 useCallback을 사용해야 할까요?3. useCallback은 어떻게 사용하나요?4. useCallback 사용 시 주의 사항5. useCallback과 React.memo의 조합6. useCallback 활용 예제7. 결론 post