이번 포스팅에서는 useCallback 과 useMemo라는 내장 Hook에 대해 알아보도록 하겠다.
< 목차 >
- Memoization
- useCallback
- useMemo
1. Memoization
useCallback 과 useMemo 함수에 대해 알기 위해서는 우선 프로그래밍에서 사용하는 메모이제이션(memoization) 기법에 대해 짚고 넘어가야 할 필요성이 있다. 메모이제이션은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. 메모이제이션의 대표적인 예로 피보나치 수열 함수를 들 수 있다.
function fibo(n) {
if (n==1) return 1
if (n==2) return 1
return fibo(n-1) + fibo(n-2)
}
위의 코드는 메모이제이션 기법을 사용하지 않고 피보나치 수열의 n번째 값을 구하는 함수이다. 해당 함수는 n의 값이 2 또는 1이 될 때까지 계속해서 함수가 돌아가는 구조이다. 이러한 구조로 함수를 만들게 되면 n의 값이 조금만 커져도 n번째 값을 구하는 과정에서 콜스택에 엄청나게 많은 함수들이 쌓이게 된다. 이는 비효율성을 야기하고 n번째 값을 구하는데 너무 많은 시간이 소요되는 결과를 초래한다.
이런 비효율성을 해소하기 위해 나온 것이 바로 메모이제이션 기법이다. 한번 계산했던 결과는 메모리에 저장해 놓은 후 같은 결과를 얻고 싶을 때 메모리에서 가져다 쓰는 기법이라고 할 수 있다. 위의 피보나치 수열 함수를 메모이제이션 기법을 적용해서 다시 만들면 다음과 같다.
let memo = {}
function fibo(n) {
let result;
if (n in memo) {
result = memo[n]
} else {
if (n == 1 || n == 2) {
result = 1
} else {
result = fibo(n-1) + fibo(n-2)
}
memo[n] = result
}
return result
}
자세히 살펴보면 반복되는 부분의 결과값은 함수를 호출해서 쓰지 않고 memo라는 객체 안에 저장해 두었다가 꺼내쓰는 방식으로 그 값을 구하고 있는 것을 알 수 있다.
1. useCallback
React 공식문서에 따르면 useCallback은 메모이제이션된 콜백을 반환한다고 되어있다. 즉, useCallback( )은 함수를 메모이제이션(memoization)하기 위해 사용하는 Hook이다. 사용 방법은 다음과 같다.
const memoizedCallback = useCallback( ()=>{} , [] )
useCallback Hook은 두 개의 인자값을 받는데 첫번째 인자값은 콜백함수이고 두번째 인자값은 배열이다. 첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용 할 수 있게 해준다.
예를 들어, 어떤 component 안에서 다음과 같은 add( ) 함수가 선언되어 있을 경우 해당 component가 리랜더링 될 때마다 새로운 함수가 생성된다.
const add = () => x + y
하지만 useCallback( )을 사용하면 해당 component가 리랜더링 되더라도 이 함수가 의존하고 있는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환하게 된다. 즉, x 또는 y의 값이 바뀌면 새로운 함수가 생성되어 add 변수에 할당되고, x 와 y의 값이 동일하다면 리랜더링이 되더라도 이 함수를 재사용 한다.
const add = useCallback(()=>{
x + y
}, [x, y])
참고로 useCallback( )의 두번째 인자값으로 빈 배열 [ ]이 들어갈 경우, component가 랜더 완료된 최초 시점에서 메모이제이션을 하게 된다.
useCallback( )에 대해 좀 더 이해해보기 위해 다음과 같은 카운터를 만들어 보았다.
import React, { useState, useCallback } from 'react'
const HelloCallback = () => {
const [hi, setHi] = useState(0)
const [bye, setBye] = useState(0)
const hiClick = useCallback(()=>{
setHi(hi+1)
},[bye])
const byeClick = useCallback(()=>{
setBye(bye+1)
},[bye])
return (
<>
<h1>Hello useCallback!!</h1>
<p></p>
<h1>
<span>{hi}</span>
<button onClick={hiClick}>hi</button>
</h1>
<h1>
<span>{bye}</span>
<button onClick={byeClick}>bye</button>
</h1>
</>
)
}
export default HelloCallback;
hi버튼을 클릭했을 때는 hiClick 함수가 실행되고 bye버튼을 클릭했을 때는 byeClick 함수가 실행된다. 여기서 hiClick 함수와 byeClick 함수 모두 useCallback( ) 함수를 사용해서 bye 변수의 상태를 추적하고 bye 변수의 상태가 변하지 않는다면 메모이제이션된 콜백을 반환하도록 하였다.
hiClick 함수의 경우 bye 변수의 상태를 추적하고 있기 때문에 bye 변수의 상태가 변하지 않는 한 메모이제이션된 콜백을 반환하게 된다. 즉, bye의 상태가 갱신되었을 때만 새로운 함수가 생성되어 hiClick에 할당되고 bye의 상태가 그대로라면 메모이제이션된 기존 함수를 재사용 하는 것이다. 그 결과 hi버튼은 bye버튼이 클릭된 이후에만 최초 1회 실행된다.
3. useMemo
useCallback이 메모이제이션된 콜백을 반환했다면 useMemo는 메모이제이션된 값을 반환하는 Hook이다. 사용하는 방법과 작동원리는 useCallback과 동일하다. 단지, useCallback이 함수를 반환한다면 useMemo는 값을 반환한다는 점에서 차이가 있다.
const [state, dispatch] = useReducer(reducer, initialState)
const defaultValue = useMemo(()=>({
state,
dispatch
}), [state])
위의 코드는 useReducer Hook을 사용해 전역에서 상태를 관리할 경우 state 와 dispatch를 객체에 담아 useMemo를 이용해 메모이제이션 기법이 적용되게끔 한 것이다.
( useReducer Hook에 대한 설명은 이전 게시글 참고 )
2022.04.29 - [React] - React - useContext & useReducer
useCallback 과 useMemo 모두 메모이제이션 기법을 사용하는 Hook으로 성능 최적화에 사용되는 함수들이다. 하지만 일반적으로 소프트웨어의 성능 최적화에는 그에 상응하는 대가가 따르게 된다. 예를 들어, useMemo 함수를 남용할 경우 component의 복잡도가 올라가기 때문에 코드 가독성과 유지보수성이 떨어지게 된다. 또한 useMemo가 적용된 레퍼런스는 재활용을 위해 가비지 컬렉션(garbage collection)에서 제외되기 때문에 메모리를 더 쓰게 될 수도 있다. 따라서 useCallback 혹은 useMemo Hook을 사용할 경우 실질적으로 얻을 수 있는 성능 상의 이점이 어느 정도인지 반드시 따져보고 사용하기를 권장한다.
'React' 카테고리의 다른 글
React - react-router-dom (0) | 2022.05.02 |
---|---|
React - Custom Hook (0) | 2022.05.01 |
React - useContext & useReducer (1) | 2022.04.29 |
React - Lifecycle & useEffect (0) | 2022.04.26 |
React - 함수 컴포넌트 & useState (2) | 2022.04.26 |