useMemo와 useCallback의 차이
개요
useMemo와 useCallback을 한 번도 사용해보지 않은 나는 이것을 면접질문으로 받았다.
다행히 30분전에 급하게 이것저것 공부하다가 메모이제이션과 useMemo와 useCallback이 각각
어떤 걸 반환하는지를 보았기 때문에 이 부분을 말할 수 있었지만 자세히 공부해 봐야겠다.
Memoization
메모이제이션이라는 개념에 대해 먼저 알고 가자. 메모이제이션은 기존에 수행한 연산의 결괏값을 어딘가에
저장해 두고 동일한 입력이 들어오면 재활용하는 기법이다.
이것을 잘 활용하면 중복되는 연산을 피할 수 있어 애플리케이션의 성능을 최적화할 수 있다.
useMemo
useMemo는 메모이제이션된 값을 봔환하는 함수이다.
useMemo(() => function, [deps])
여기서 의존성배열 안에 값을 지정한다면 그 값이 변경될 때마다 함수를 실행하고, 그 함수의 보안한 값을 봔한해준다.
import React, { useState, useCallback, useMemo } from "react";
export default function App() {
const [num, setNum] = useState(0);
useMemo(() => {console.log(num)}, [num]);
return (
<>
<button onClick={() => setNum((prev) => (prev + 1))}>X</button>
</>
);
}
위 코드를 예시로 들면 X 버튼을 눌렀을 시 num의 값을 증가시키고, 의존성배열 안에 있는 num이 변하니 console이 출력된다.
useMemo를 왜 쓸까?
import { useState } from "react";
function App() {
const [num, setNum] = useState(0);
console.log(num);
return (
<div className="App">
<button onClick={() => setNum((prev) => prev + 1)}>X</button>
</div>
);
}
export default App;
state가 변하면 리렌더링이 일어나고 결국에는 console.log만 적어줘도 console에 찍히는데 왜 useMemo를 사용할까?
그 이유는 부모 컴포넌트에 의해서 리렌더링이 될 경우 상태 값에 관계없이 console.log가 찍히는 연산이 발생한다.
만약 이게 console.log가 아니고 몇 초씩 걸린다면 답답할 것이다.
따라서 특정 값이 변할 경우에만 연산을 실행할 수 있도록 useMemo를 사용하는 것이다.
그럼 useEffect랑은 뭐가 다른 거지..?라고 생각하였다.
결론만 말하자면 둘의 쓰임이 다르다.
useEffect는 컴포넌트의 생명주기와 관련돼서 특정작업을 할 때 쓰인다. 예를 들어 마운트 업데이트 원마운트등을 다
의존성배열로 하는 반면 useMemo는 똑같은 연산반복을 방지하는 차원으로 쓰이기 때문에 사실상 의존성배열 안에 값이 있어야 한다.
이게 내가 이해한 둘의 차이다.
useCallback
앞서 보았던 useMemo는 메모이제이션 된 값을 봔환하였는데 useCallback은 메모이제이션된 함수를 봔한하는 특징을 가지고 있다.
useCallback 또한 의존성배열 안에 값이 변하면 등록한 함수를 반환한다.
useMemo(() => console.log(), [test])
const memoizedCallback = useCallback(() => console.log(), [test])
하지만 useCallback은 함수를 봔환하기 때문에 그 함수를 가지는 변수가 필요하다.
useCallback 사용예시
수많은 사용예시가 있지만 그중 가장 이해하기 쉬웠던 예시를 적어보겠다.
자식 컴포넌트에 props로 함수를 전달할 경우 쓰인다.
컴포넌트에서 특정함수를 정의할 경우 각각의 함수들은 모두 고유한 함수가 된다.
이런 고유함수가 부모를 통해 props로 자식에게 전달한다면 자식은 props가 변경되었다고 판단해 리렌더링이 일어난다.
function App() {
const [name, setName] = useState('');
const onSave = () => {};
return (
<div className="App">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Profile onSave={onSave} />
</div>
);
}
여기서 name이 변경되면 onSave함수가 만들어지고 props로 전해진다. 이럼 name이 변경될 때마다 앞에서 말한 것이 반복되어 결국에는
리렌더링이 일어난다.
import React, { useCallback, useState } from 'react';
import Profile from './Profile';
function App() {
const [name, setName] = useState('');
const onSave = useCallback(() => {
console.log(name);
}, [name]);
return (
<div className="App">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Profile onSave={onSave} />
</div>
);
}
따라서 위와 같이 useCallback을 사용해 onSave라는 함수를 재사용하는 것으로 자식 컴포넌트의 리렌더링을 방지할 수 있는 것이다!
결론
useMemo와 useCallback의 개념을 이해해 보았다. 확실히 코드로 짜보니 이해가 더 잘되는 거 같다.
이제부턴 개발할 때 저 2개의 hook들도 사용하며 성능을 개선해 보며 코딩할 거 같다.