오늘은 헷갈린 Hook들에 대해 정리해보려고 합니다. useState와 useEffect보다는 useMemo, useCallback이 다소 헷갈리기 때문에 해당 글을 작성하게 되었습니다. useEffect의 라이프사이클 관련 정리도 필요해 보이지만 그건 추후 추가된 글에서 다뤄보겠습니다.
"useMemo와 useCallback이 왜 필요한가?"를 먼저 생각해보겠습니다. 리액트 내장 훅인 이 두 함수가 우리에게 편리함을 제공해준다면, 분명히 필요성이 제시되었기 때문에 누군가 개발했다는 것을 의미를 합니다. 가장 핵심적인 키워드는 '렌더링 최적화'입니다. 예시를 하나 들어보겠습니다.
컴포넌트가 렌더링 되었다는 것은 사용자가 해당 함수(컴포넌트)를 호출시켰다는 것을 의미하며, 이 과정에서는 내부에 있는 변수 또는 함수들은 매번 다시 선언됩니다. 컴포넌트는 자신의 state가 변경되거나, 부모 컴포넌트로부터 받은 props가 변경되었을 때 리렌더링이 일어나게 됩니다. 이 경우, 하위 컴포넌트가 최적화된 상태가 아니라면 부모 컴포넌트로부터 받은 props가 변경되지 않더라도 리렌더링이 일어나게 되는데, 여기서 문제가 발생합니다. A 컴포넌트가 여러 개의 state, props를 받는 상태에서 하나의 state가 변경된다면 나머지 state들도 모조리 다시 계산되어야 합니다. 이러한 불필요한 렌더링이 일어나는 경우를 대비하여 React Rendering 최적화를 하기 위해서는 메모이제이션(Memoization) 기능을 지원하는 리액트의 내장 훅인 useMemo와 useCallback이 필요한 것입니다.
추가적으로, 리렌더링 조건은 다음과 같다.
→ 자신의 state가 변경될 때
→ 부모로부터 받은 props가 변경될 때
→ 부모 컴포넌트가 리렌더링 될 때
→ forceUpdate()를 실행하였을 때
메모이제이션(Memoization)이란?
메모이제이션은 일종의 *캐싱과 같습니다. 특정 연산을 반복해야 할 때 사용되는 기법으로, 반복되는 결과를 메모리에 저장하여 중복되는 연산 없이 빠른 실행을 가능하게 해 줍니다.
(*캐시(cache) : 데이터나 값을 미리 복사해놓는 임시 장소를 의미합니다. 캐시는 접근 시간에 비해 원래 데이터에 접근하는 시간이 오래 걸리는 경우나 연산을 반복해야 하는 시간을 절약하고 싶을 때 사용됩니다. 데이터를 미리 복사해놓으면 계산이나 접근 시간 없이 더 빠른 속도로 데이터 접근이 가능합니다.)
이러한 메모이제이션 기능을 지원해주는 훅인 useMemo와 useCallback은 퍼포먼스를 최적화해준다는 점에서 같지만 그 쓰임새가 다른데, useMemo의 경우에는 메모이제이션된 값을 반환을 하고, useCallback의 경우에는 메모이제이션된 콜백을 반환합니다. 그 차이점은 useCallback은 전달된 함수 그 자체를 캐싱하지만, useMemo는 전달된 함수가 실행되고 반환된 결과값을 캐싱한다는 점에 있습니다.
useMemo
useMemo(() => { /* 로직 */ }, []);
useMemo의 첫 번째 인자에는 연산을 마치고 반환하는 함수를 주고, 두 번째 인자는 의존하는 상태값(배열)을 넣어준다. 여기서 이 배열은 depedencies array라고 부르고 줄여서는 deps라고 부른다. 즉, deps에 들어간 state 또는 props가 변경되었을 때 첫 번째 인자의 로직이 실행된다.
useCallback
const [sum, setSum] = useState<number>(0);
const addNum = useCallback(() => {
setSum(num + 1);
}, [num]);
useCallback도 비슷하다. 첫 번째 인자에 들어가는 함수는 오직 의존하는 상태값(deps)이 변경된 경우에만 갱신이 되며, 위 예제의 경우에는 num이 변경될 시에 1만큼 더 해서 useState로 선언된 sum값에 넣어주는 형식이다.
실제 예제.
const [detail, setDetail] = useState<boolean>(false);
const toggleDetail = useCallback(() => {
setDetail(prev => !prev);
}, []);
const onAccept = useCallback(async () => {
try {
// 서버 요청 API
} catch (error) {
// 에러 핸들링
}
// Redux Store 로직
}, [accessToken, dispatch, item.orderId, navigation]); //필요한 deps
const onReject = useCallback(() => {
// 로직
}, [dispatch, item.orderId]); // deps
'Frontend > 상태관리(Redux, Storage, ContextAPI)' 카테고리의 다른 글
[React Native] Async-Storage / Encrypted-Storage 필요성과 사용법 (0) | 2022.04.03 |
---|---|
[React Native] Redux-toolkit 적용 (0) | 2022.04.02 |
[React Native] Context API with Hook (0) | 2022.02.21 |