useMemo : 1번째 인자로 받은 값을 캐싱 하고 있는 Hooks, 2번째 인자가 바뀌지 않는 한 다시 실행되지 않음.
Hooks로 바뀌면서 리렌더 될 떄마다 로또 숫자를 뽑는 함수인 getWinNumber() 가 계속 호출되는 문제가 있어서 useMemo를 사용해야 했다.
const lottoNumbers = useMemo(()=> getWinNumbers(), []); //2번째 인자로 넘겨준 배열안의 값이 바뀌지 않는 이상 재실행되지 않는다.
const [winNumbers, setWinNumbers] = useState(lottoNumbers);
하지만 아래 링크의 강의내용에서도 같은 내용이 나오는데 useState에서 두 번째 파라미터로 함수자체를 넣어주면 알아서 lazy init을 해준다고 했는데 이번에는 다른 방식으로 해결하는 것이 참 인상깊었다.
( section2. 3-8. 숫자야구 Hooks로 전환하기(+useState lazy init))
useMemo vs useRef
useMemo : 복잡한 함수 결과값을 기억
useRef : 일반 값을 기억
useMemo vs useCallback
useMemo : 함수 결과값을 기억한다.
useCallback : 함수 자체를 기억한다.
const onClickRedo = useCallback(()=>{
setWinNumbers(getWinNumbers());
setWinBalls([]);
...
},[]);
이렇게 useCallback을 사용하면 함수 컴포넌트가 리렌더 되면서 onClickRedo가 재생성 되는 것을 방지할 수 있다.
만약에 함수 생성하는 것 자체가 너무 오래걸려서 함수 생성 자체가 비용이 크다면 함수 자체를 기억해둘 수 있다.
(useCallback을 사용하지 않으면 함수 컴포넌트는 리렌더 되면 해당 함수 컴포넌트 자체가 재실행 되기 때문에 원래라면 onClickRedo같은 함수도 재생성된다고함.)
그렇다면 useCallback으로 모든 함수를 감싸면 이득인가?
반은 맞고 반은 틀리다고 함.
useCallback안에서 쓰는 state들은 항상 2번째 파라미터에도 값을 적어주어야 한다.
const onClickRedo = useCallback(()=>{
console.log(winNumbers);
setWinNumbers(getWinNumbers());
setWinBalls([]);
...
},[]);
이렇게 state를 썼는데 2번째 파라미터에 값을 넘겨주지 않으면??? winNumbers가 최신화가 되지 않아서 해당 함수는 예전 state를 사용하게 된다.... winNumbers는 바뀌었는데 기억을 너무 잘해서 예전꺼를 그대로 사용하는 문제가 발생한다.
(강의 09:00 참고)
const onClickRedo = useCallback(()=>{
console.log(winNumbers);
setWinNumbers(getWinNumbers());
setWinBalls([]);
...
},[winNumbers]);
이렇게 useMemo나 useCallback은 둘 다 기억을 하는데 너무 기억을 잘하면 이전값을 사용해버리는 문제가 생기기 때문에 잊어버릴 필요도 있는데.. 언제 잊어버려야하는가? 이 두 번째 배열의 요소들이 바뀌었을 때이다.
useCallback 필수로 적용해야할 때
자식 컴포넌트에 함수를 넘길 때는 useCallback을 꼭 해줘야 한다.
useCallback이 없다면 매번 새로운 함수가 생성된다. 그럼 자식 컴포넌트는 어? 부모로 받은 프롭스가 바뀌었네? 하면서 매번 새로 렌더링을 해버린다고 한다.