리액트(React)에서 useMemo
와 useCallback
은 성능 최적화를 위해 사용되는 훅(hooks)입니다. 이들은 불필요한 연산과 렌더링을 방지하여 애플리케이션의 효율을 높이는 데 도움을 줍니다. 각각의 훅이 어떻게 작동하는지와 사용 사례를 아래에서 자세히 살펴보겠습니다.
useMemo
useMemo
는 메모이제이션된 값을 반환하는 훅입니다. 이 훅은 계산 비용이 많이 드는 함수의 결과값을 저장해두었다가, 의존성 배열에 있는 값이 변경되었을 때만 함수를 다시 실행하여 값을 계산합니다. 그 외의 경우에는 메모이제이션된(저장된) 값을 재사용함으로써 성능을 향상시킵니다.
사용법:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
사용 사례:
- 계산 비용이 높은 함수의 결과를 저장: 피보나치 수열 계산, 복잡한 리스트 필터링 등 비용이 많이 드는 연산을 수행하고 그 결과를 저장하여 재계산을 방지할 수 있습니다.
- 렌더링 최적화: 부모 컴포넌트가 재렌더링될 때마다 동일한 결과를 내는 복잡한 계산을 다시 수행하는 것을 방지합니다.
useCallback
useCallback
은 메모이제이션된 콜백 함수를 반환하는 훅입니다. 주로 이벤트 핸들러나 전달 콜백에서 사용되며, 의존성 배열의 값이 변경될 때만 함수를 다시 생성합니다. 이를 통해 자식 컴포넌트에 props로 콜백을 전달할 때 불필요한 렌더링을 방지할 수 있습니다.
사용법:
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
사용 사례:
- 이벤트 핸들러 최적화: 자식 컴포넌트에 전달되는 이벤트 핸들러가 자주 재생성되는 것을 방지하여, 자식 컴포넌트의 불필요한 리렌더링을 막을 수 있습니다.
- 함수 전달 최적화: 특정 함수를 자식 컴포넌트나 다른 함수에 파라미터로 넘겨줄 때, 그 함수의 재생성을 최소화하여 성능을 유지합니다.
정리
useMemo
와 useCallback
모두 리액트의 렌더링 성능을 최적화하기 위해 사용됩니다. useMemo
는 값의 메모이제이션을 통해 계산 비용을 줄이는 데 초점을 맞추며, useCallback
은 함수의 재생성을 최소화하여 자식 컴포넌트의 불필요한 리렌더링을 방지하는 데 유용합니다. 이 훅들을 적절히 사용하면 애플리케이션의 성능을 크게 향상시킬 수 있습니다.
아하 그렇군요. 그럼 useMemo의 렌더링 최적화에 관한 추가 질문이 있어요. '부모 컴포넌트가 재렌더링 될 때마다 동일한 결과를 내는 복잡한 계산을 다시 수행하는 것을 방지' 하는 상황이 다음 두가지에 모두 해당될까요?
- 부모 컴포넌트에서 복잡한 계산을 const memoizedValue = useMemo(.....)로 저장해둔 뒤 memoizedValue 를 자식 컴포넌트에게 props로 보내는 상황
- 자식 컴포넌트에서 복잡한 계산을 const memoizedValue = useMemo(.....)로 저장해 놓았는데, 부모 컴포넌트의 재렌더링이 일어나는 상황
useMemo
를 사용하는 두 가지 상황에 대해 설명드리겠습니다. 두 경우 모두 useMemo
는 계산의 재실행을 방지하고 성능을 최적화하는 데 사용될 수 있습니다.
- 부모 컴포넌트에서 복잡한 계산을 저장 후 자식 컴포넌트에게 props로 전달하는 경우:
이 경우, 부모 컴포넌트가 재렌더링 될 때마다useMemo
를 사용하여 복잡한 계산의 결과를 저장하고, 이 결과를memoizedValue
라는 변수에 할당합니다. 이 값은 자식 컴포넌트에게 props로 전달되며, 부모 컴포넌트의 의존성 배열 내의 값이 변경되지 않는 한,useMemo
에 의해 생성된 값은 재계산되지 않습니다. 이로 인해 자식 컴포넌트에게 전달되는 값이 안정적이며, 부모 컴포넌트의 재렌더링에 의해 발생할 수 있는 불필요한 계산을 방지합니다. - 자식 컴포넌트에서 복잡한 계산을 저장하고 부모 컴포넌트가 재렌더링되는 경우:
자식 컴포넌트 내에서useMemo
를 사용하여 복잡한 계산을 수행하고 그 결과를 메모이제이션합니다. 이 경우, 부모 컴포넌트가 재렌더링되어도 자식 컴포넌트의useMemo
에 명시된 의존성 배열 내의 값들이 변하지 않는 한, 자식 컴포넌트의 계산된 값은 재계산되지 않습니다. 이는 자식 컴포넌트의 의존성 값들이 변경되지 않으면, 자식 컴포넌트의 내부 상태가 유지되기 때문입니다.
결론적으로, 두 경우 모두 useMemo
는 복잡한 계산의 결과를 메모이제이션하여 재계산을 방지하고, 이를 통해 리액트 컴포넌트의 성능을 향상시키는 데 도움을 줍니다. 부모 컴포넌트의 재렌더링이 자주 발생하더라도, 의존성 배열 내의 값들이 변하지 않는다면, useMemo
를 사용한 계산은 재실행되지 않아 성능이 최적화됩니다.
감사합니다. 그러면 다음으로 useCallback 에 대한 추가 질문이 있습니다.
알려주신 대로 썼을때 콜백함수로 써야 하는 것은 memoizedCallback 인가요?
그러니까 만약 onClick 에 적용한다고 했을때, onClick={memoizedCallback} 이런식으로요
네, 맞습니다. useCallback
을 사용할 때, memoizedCallback
은 메모이제이션된 콜백 함수로서 사용됩니다. 이 함수는 useCallback
에 전달된 의존성 배열 [a, b]
내의 값들이 변경되지 않는 한 재생성되지 않습니다. 이 방법을 통해 함수의 재생성을 최소화하고, 결과적으로 리액트 컴포넌트의 불필요한 리렌더링을 방지할 수 있습니다.
예를 들어, onClick
이벤트 핸들러에 memoizedCallback
을 사용하는 경우 다음과 같이 작성할 수 있습니다:
<div onClick={memoizedCallback} />
이렇게 구현하면, div
요소에 클릭 이벤트가 발생할 때마다 memoizedCallback
함수가 호출됩니다. 그리고 이 함수는 의존성 배열의 값인 a
와 b
가 변하지 않는 한, 동일한 함수 참조를 유지합니다. 이는 특히 이벤트 핸들러가 props로 전달되거나 복잡한 컴포넌트 트리에서 사용될 때 매우 유용합니다. 그 이유는 함수의 안정성이 자식 컴포넌트가 불필요하게 다시 렌더링되는 것을 방지하기 때문입니다.
useCallback
을 사용할 때는 메모리 사용과 관련된 추가 비용을 고려해야 합니다. 모든 함수를 useCallback
으로 감싸는 것이 항상 좋은 선택은 아니며, 주로 자식 컴포넌트에 함수를 전달할 때나 렌더링 최적화가 필요한 경우에 사용하는 것이 좋습니다.
아하 그렇군요! 그러면 질문이 하나 더 있습니다.
const [state, setState] = useState(false)
위와 같이 useState를 하나 만들었고 이는 모달창을 켜고 끄는데(false 면 꺼지고 true면 꺼지는 식)사용되는 상황입니다.
그런데 자식 컴포넌트에서도 모달을 끄는, 즉 state 를 false로 만들어야 하는 상황이 있다면 저는 자식 컴포넌트에게 props 로 setState를 넘겨주고, 자식컴포넌트에서 이를 활용하여 setState(false)하는 식으로 처리했었습니다. 이렇게 사용하는 것이 일반적일까요?
아니면 setState를 또 useCallback 으로 감싸서 처리해야 하는 걸까요?
useState
로 생성된 setState
함수를 자식 컴포넌트에 props로 넘겨주어 사용하는 것은 리액트에서 매우 일반적인 패턴입니다. 이 방법은 특히 상태를 여러 컴포넌트 간에 공유하고 조작해야 할 때 유용합니다.
setState
함수는 리액트에 의해 안정화되어 있기 때문에, 이 함수 자체는 컴포넌트 렌더링 간에 변하지 않습니다. 따라서 setState
함수를 useCallback
으로 감싸는 것은 필요하지 않습니다. useCallback
은 컴포넌트의 리렌더링 동안 함수가 재생성되는 것을 방지할 필요가 있을 때 유용합니다. 하지만 setState
는 리액트 훅에서 반환된 함수이며, 자체적으로 메모이제이션이 적용되어 있어 안정적인 참조를 유지합니다.
예를 들어, 모달을 닫는 기능을 자식 컴포넌트에 전달하는 것은 아래와 같이 간단하게 처리할 수 있습니다:
// 부모 컴포넌트
const [modalOpen, setModalOpen] = useState(false);
return (
<ChildComponent closeModal={() => setModalOpen(false)} />
);
// 자식 컴포넌트
function ChildComponent({ closeModal }) {
return (
<button onClick={closeModal}>Close Modal</button>
);
}
위의 예제에서 closeModal
함수는 부모 컴포넌트에서 선언되었고, setModalOpen(false)
를 호출하는 간단한 함수입니다. 이 함수는 자식 컴포넌트에서 사용될 때 항상 동일한 참조를 유지하므로 자식 컴포넌트의 불필요한 리렌더링을 일으키지 않습니다.
따라서, setState
함수를 자식 컴포넌트에 직접 전달하거나 간단한 래퍼 함수로 감싸서 전달하는 것은 일반적이고 안전한 방법입니다. useCallback
은 해당 함수의 재생성을 방지할 필요가 있을 때만 고려하면 충분합니다.
'react' 카테고리의 다른 글
[240515 TIL] jsx, useState, props, 불변성 등 (0) | 2024.05.15 |
---|---|
[240514 TIL] 리액트 리렌더링 (0) | 2024.05.14 |
[리액트 11] 리액트 라우터 (1) | 2024.01.09 |
[리액트 10] Redux (1) | 2024.01.04 |
[리액트 9] Context API (1) | 2024.01.04 |