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 값이 변경되지 않는 한, 재호출되지 않는다.
useMemouseCallbacksetState나 dispatch 함수 등을 호출하는 경우useMemo는 계산 집약적인 작업에 가장 적합하며, useCallback은 의존성이 거의 없는 가벼운 기능에 가장 적합하다.useMemomap 혹은 filter 등을 사용하여 이후 렌더링에서도 동일한 참조를 사용할 가능성이 높을 경우useCallbackReact.memoHOC(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-함수와-연산된-값-재사용