지금까지 리액트 관련 프로젝트를 진행할 때 useMemo, useCallback 개념은 알고는 있었지만 사용해 본 기억이 극히 드물었기 때문에 실제 어느 상황에 쓰면 좋을지 알기 위해서 복습겸 다시 공부해보는 시간을 가져볼 것이다.
연산된 결과 값을 특정 배열(메모리)에 저장해 두고 이전 값과 비교했을 때 결과가 동일하면 재사용해주는 기법이다.
리액트에서 제공하는 메모이제이션 종류는 다음과 같다.
React.memo(컴포넌트), useMemo(() => 함수의 리턴 값, []), useCallback(() => {함수 자체}, [])
[]
배열의 값(dependency)을 기준으로 변경사항들을 확인한다.메모이제이션 된
함수
를 반환한다.
useCallback(() => 함수, [deps])
값이 있으면 deps
에 의존하고 있는 값이 변하면 함수
에 등록된 함수를 반환한다.const
변수에 초기화 시켜주기.react 가 재렌더링 되는 조건은 크게
props, state
가 변경 되었을 때 변경이 된다. 즉, 불러오는데 오래 걸리거나 무거운 api를 불러오는 부분도 매 번 다시 불러온다는 뜻인데,useCallback
hook을 사용해서 컴포넌트가 재렌더링이 되어도 api를 불러오는 함수의 재렌더링을 막아 볼 것이다.
// 부모 컴포넌트
const Home = () => {
const [ex, setEx] = useState(0);
const [watch, setWatch] = useState(0);
// ex 변수가 변경될 때만 fetch가 동작하도록 하기.
function exManyAPI() {
fetch("https://jsonplaceholder.typicode.com/posts")
.then(res => res.json())
.then(data => {
console.log(data);
return data;
})
}
// watch 값이 변경될 때만 재렌더링 시켜주기.
const getAPI = useCallback(() => {exManyAPI()}, [watch]);
console.log('parent re-render');
return (
<div>
<button onClick={() => setEx((prev) => (prev + 1))}>X</button>
<button onClick={() => setWatch((prev) => (prev + 1))}>Y</button>
{/* Profile 이라는 자식 컴포넌트에 useCallback 함수가 적용된 apiGet 함수를 보내주기. */}
{/* props로 보내지는 onSave 함수에 getApi가 아닌 exManyAPI를 넣어주면
부모 컴포넌트의 state가 변경될 때 마다 값을 새로 불러 줄 것이다.*/}
<Profile onSave={getAPI} ex={ex}/>
</div>
);
}
// 자식 컴포넌트
function Profile({onSave, ex}) {
// 부모 컴포넌트의 watch를 변경시켜주는 버튼을 누르면 재렌더링이 되고,
// ex state를 변경 시켜주는 버튼을 눌렀을 때는 onSave() === apiGet() 함수가 재렌더링이 되지 않는다.
useEffect(() => {
onSave();
// 부모의 값이 변경되면 자식한테도 변경된 값을 다시 보내줘야되기 때문에 [] 안에 onSave 넣어주기.
}, [onSave]);
return(
<div>
<p>profile {ex}</p>
</div>
)
}
getApi
함수는useCallback
으로 감싸져 있고 두 번째 인자로watch
를 넣어줬기 때문에
watch state
를 변경해주는 함수를 넣었을 때 api 함수를 다시 불러오는 것을 볼 수 있고, 반대로ex state
를 변경시켜주는 버튼을 눌렀을 때 재렌더링이 되지 않는 것을 볼 수 있다.
useMemo
로 이전에 연산한 값을 재사용하는 메모이제이션을 적용할 수 있다.
const Home = () => {
const [item, setItem] = useState("");
const [itemList, setItemList] = useState([]);
const handleItem = (event) => {
setItem(event.target.value);
}
const handleSubmit = (event) => {
setItemList([...itemList, item]);
event.preventDefault(); // form submit event로 인해 재로딩되지 않도록 해주기
setItem("");
};
// 주목해야 될 부분!
const itemCount = (item) => {
console.log("item 개수를 셉니다");
return item.length;
};
// count을 이렇게 두면 input창이 변경 될 때마다 console에 값이 계속 늘어난다.
// const count = itemCount(itemList);
// 여기까지 주목하기~!
// count 을 변경시켜주기.
const count = useMemo(() => itemCount(itemList), [itemList]);
return (
<>
<form onSubmit={handleSubmit}>
<input onChange={handleItem} value={item} />
<button type="submit">submit</button>
</form>
<p>{itemList}</p>
<p>itemList 개수 : {count}</p>
</>
);
}
export default Home;
검색처럼 입력 때 마다 연관 검색어가 생기는 등의 기능을 구현할 때 말고 로그인, 회원가입 같이 입력이 다 되었을 때의 결과만 필요로 하는 부분 또는 컴포넌트 재렌더링이 사소한 동작에 인해 생기게 되는 부분이 있을 때 사용하면 좋을 것 같다는 생각이 든다.