useMemo
, useCallback
재귀를 이용한 피보나치 함수
// 총 6가지 경우의 n : f(5)~f(0)
// n이 2보다 작을 경우(0,1) 그대로 반환
let a = 0;
const fib = (n) => {
a += 1;
console.log(a +'번째 연산: n =', n);
if (n < 2) return n
return fib(n - 1) + fib(n - 2)
}
fib(5); // -> 5
fib(5)
fib(4) + fib(3)
fib(3) + fib(2) + fib(2) + fib(1)
fib(2) + fib(1) + fib(1) + fib(0) + fib(1) + fib(0) + 1
fib(1) + fib(0) + 1 + 1 + 0 + 1 + 0 + 1
1 + 0 + 1 + 1 + 0 + 1 + 0 + 1 = 5
//
메모이제이션 피보나치 함수
// 이미 연산한 값은 배열에 각 단계의 값을 저장
// fib
let a = 0;
const memo = [0, 1];
const fib = (n) => {
a += 1;
console.log(a +'번째 연산: n =', n);
if (memo[n] || n <= 1) return memo[n]
const result = fib(n - 1) + fib(n - 2)
memo[n] = result
return result
}
fib(5); // -> 5
memo // -> (6) [0, 1, 1, 2, 3, 5]
fib(5)
fib(4) + fib(3) // memo[5] = 3 + 2 = 5 -> memo = [0, 1, 1, 2, 3, 5]
fib(3) + fib(2) + 2 // memo[4] = 2 + 1 = 3 -> memo = [0, 1, 1, 2, 3]
fib(2) + fib(1) + 1 + 2 // memo[3] = 1 + 1 = 2 -> memo = [0, 1, 1, 2]
fib(1) + fib(0) + 1 + 1 + 2 // memo[2] = 1 + 0 = 1 -> memo = [0, 1, 1]
useMemo
메모이제이션된 값을 반환하는 리액트 훅
직전에 연산된 값이 있다면, 다시 연산을 하지 않고, 해당 값을 반환
useMemo(값을 연산하고 반환하는 함수, 의존성 배열)
// 문제 상황
const [val1, setVal1] = useState(0)
const [val2, setVal2] = useState(0)
const val3 = val1 * val1
console.log('val3:', val3)
return (
<>
<div>val1: {val1}</div>
<div>val2: {val2}</div>
<div>val3: {val3}</div>
<button onClick={() => setVal1(v => v+1)}>Add val1</button>
<button onClick={() => setVal2(v => v+1)}>Add val2</button>
</>
);
val3
는 val1
에만 의존하는데 val2
가 변경됐을 때도 컴포넌트가 리렌더링 되어 연산이 재실행
// val3에 useMemo 적용
const val3 = useMemo(() => {
console.log('val3:', val1 * val1)
return val1 * val1
}, [val1])
UseCallback
메모이제이션된 콜백 함수, 즉 이미 생성된 함수를 반환하는 리액트 훅
불필요한 렌더링을 방지하기 위해 참조의 동일성을 보장하거나, 자식 컴포넌트에 의존적인 콜백 함수를 전달할 때 유용
useCallback(생성된 함수를 반환하는 함수, 의존성 배열)
함수 동등성
- 자바스크립트에서 함수는 객체로 취급이 되기때문에, 함수를 동일하게 만들어도 메모리 주소가 다르면 다른 함수로 간주한다.
- 특정 함수를 다른 함수의 인자로 넘기거나, 자식 컴포넌트의 props로 넘길 때 함수의 참조가 달라서 예상하지 못한 성능 문제가 생길 수 있다.
- 이때,
useCallback
을 이용해 함수를 특정 조건이 변경되지 않는 이상 재생성하지 못하게 제한하여 함수 동등성을 보장할 수 있다.
// 문제 상황
const [data, setData] = useState(null);
const fetchData = () =>
fetch("https://jsonplaceholder.typicode.com/todos/${id}")
.then((response) => response.json())
.then((data) => setData(data));
useEffect(() => {
fetchData();
}, [fetchData]);
컴포넌트가 렌더링 될 때마다 fetchData
함수가 새로운 참조값으로 변경
fetchData
함수 변경 useEffect
-> 재렌더링 -> 무한루프
// useCallback 적용
const fetchData = useCallback(
() =>
fetch("https://jsonplaceholder.typicode.com/todos/${id}")
.then((response) => response.json())
.then((data) => setData(data)),
[id]
);
컴포넌트가 다시 렌더링 되더라도 fetchData
참조값 동일하게 유지
useEffect에 의존성 배열 값에 있는 fetchData 함수는 id
값이 변경되지 않는 한, 재호출되지 않는다.
useMemo
useCallback
setState
나 dispatch
함수 등을 호출하는 경우useMemo
는 계산 집약적인 작업에 가장 적합하며, useCallback
은 의존성이 거의 없는 가벼운 기능에 가장 적합하다.useMemo
map
혹은 filter
등을 사용하여 이후 렌더링에서도 동일한 참조를 사용할 가능성이 높을 경우useCallback
React.memo
HOC(Higher-Order Components): 컴포넌트를 인자로 받아 새로운 컴포넌트를 다시 반환해주는 함수
일반 컴포넌트는 인자로 받은 props를 UI에 활용하는 반면,
인자로 받은 컴포넌트를 새로운 별도의 컴포넌트로 만든다.
const MyComponent = React.memo((props) => { return (컴포넌트 렌더링 코드) });
React.memo
는 HOC이기 때문에 클래스형 컴포넌트, 함수형 컴포넌트 모두 사용 가능하지만,useMemo
는 hook이기 때문에 오직 함수형 컴포넌트 안에서만 사용 가능
useMemo와 useCallback는 왜, 언제 사용할까? | Hayeon Dev Blog
useMemo와 useCallback의 차이
https://velog.io/@vvsogi/React-useMemo-useCallback의-사용-이유와-사용법
React.memo와 useMemo 차이점
https://velog.io/@uoayop/useCallback-useMemo-함수와-연산된-값-재사용