해당 내용은 https://www.udemy.com/course/best-react/ 강의를 들으며 정리하고 스스로 공부 한 내용을 기록 하였습니다.
지난 포스팅 (React.memo
) 에서 말했듯이 props를 통한 객체나 배열 또는 함수를 가져오는 컴포넌트에는 어떻게 해결 할 지에 대해 알아보도록 하자.
useCallback()
컴포넌트 실행 전반에 걸쳐 함수를 저장 할 수 있게 하는 훅
리액트에 함수를 저장하고 실행할 때 마다 함수를 재생성 할 필요가 없다는 것을 알려준다.
강의를 복습 하면서 useCallback
부분이 잘 이해가 가지않아 따로 검색해보았는데,
사실 useCallback
은 새로 생성되지 않아서 동일 참조 값을 사용하는 게 아니라 매 렌더마다 항상 함수의 생성까지는 되는데, 의존성이 바뀌지 않았다면 생성된 함수를 무시하고 기존 함수를 반환 한다고 한다.
참고: Hook 자주 묻는 질문 – React
let obj1 = {};
let obj2 = {};
obj1 === obj2 // false
obj1 = obj2;
obj1 === obj2 // true
우리가 선택한 함수를 리액트의 내부 저장 공간에 저장해서 함수 객체가 실행 될 때 마다 이를 재사용 할 수 있다.
사용법은 저장하려는 함수를 래핑 해주면 된다.
const toggleParagraphHandler = useCallback(() => {
setShowParagraph((prev) => !prev);
}, []);
해당 예시처럼 useCallback
훅을 사용하고, 함수를 첫 번째 인자로 전달하면 useCallback
은 이 저장된 함수를 반환 해준다.
그리고 App 함수 가 다시 실행되면 useCallback
이 리액트가 저장한 함수를 찾아서 같은 함수 객체를 재사용 한다.
useCallback
은 어떤 함수가 절대 변경 되어서는 안될 때 자주 쓰인다.
또한 두 번째 인자가 필요한데, 두 번째 인자는 useCallback
호출에 대한 의존성 배열 이 들어와야 한다. (useEffect
와 같은 의미를 가지고 있다.)
내용이 없는 의존성 배열이 온 경우에는 리액트에게 이 콜백 함수는 절대 변경되지 않을 것이라고 알려주는 배열이다.
따라서 App 컴포넌트가 다시 렌더링 되어도 항상 같은 함수 객체가 사용된다.
useCallback
은 메모리 안에서 항상 같은 함수 객체 임을 보증한다.
useCallback
에서 의존성 배열은 늘 최신 값을 가져와 기존 값과 변경이 되었는지 비교 후 기존 함수를 반환 할지 새로운 함수 객체를 반환 할지 결정한다.
최신 값을 가져올 수 있는 것은 자바스크립트의 함수는 모두 클로저 함수 이기 때문이다. ( 참고 : https://ko.javascript.info/closure 해당 페이지에서 도움을 많이 받았다.)
이 의존성 배열의 필요성을 강의에서 들었던 내용의 코드를 가져와서 약간의 설명을 해보자면,
만약, 위 사진에서 의존성 배열에 들어가있는 allowToggle
이 없다고 가정해보자.
App함수에 있는 블록 안의 함수들이 정의가 될 때, 이 App함수 내부에서 사용 되는 모든 변수들을 잠그게 된다.
나는 이를 하나의 컨텍스트를 생성한다 라고 생각하며, 여기서 allowToggle
에 집중하여 보면 될 것 같다.
이는 변수나 상수가 될 것이며, App함수 내부의 함수안에서 사용하고 있기도 하다.
자바스크립트는 이 상수에 클로저를 만들고 함수를 정의할 때 사용하기 위해 상수를 저장하게 되는데, 이렇게 되면 이 toggleParagraphHandler
가 실행이 되면 이 저장된 상수를 그대로 사용할 수 있는 것이다.
즉, 이 변수의 값은 변수가 저장된 시점의 값을 사용하는 것 이다.
이 기능을 사용하면 함수 밖의 변수를 함수 안에서 쓸 수 있으며 우리가 원하는 시점에 함수를 호출 할 수 있다.
하지만 useCallback
을 사용하여 리액트에게 해당 함수를 메모리 어딘가에 저장하도록 지시하게 되는데, 이를 사용하면 allowToggle
의 값은 최신 값이 아닌 App 컴포넌트가 처음 실행된 시점의 값을 저장하고 있기 때문에 문제가 발생한다.
따라서 의존성 배열에 allowToggle
을 종속형태로 추가하여 최신 값을 가져오도록 지시하여 새로운 값이 들어오면 새로 생성한 함수를 반환 하도록 도와주게 된다.
리액트 컴포넌트에서는 상태와 props, 컨텍스트를 이용해 작업할 수 있으며 props와 컨텍스트는 결국 상태의 변경으로 이어지며, 변경된 상태가 있는 컴포넌트는 재평가, 즉 컴포넌트 함수가 재 실행 된다는 것을 알아보았다.
리액트는 단순히 최신 평가의 결과를 가져와서 직전 평가의 결과와 비교하며, 확인된 모든 변경 사항이나 차이점을 리액트 DOM에 전달한다.
이 리액트DOM 은 변경 사항을 브라우저의 실제 DOM에 적용 하고 변경 되지 않은 것들을 그대로 둔다.
리액트가 컴포넌트를 재평가 할 때 단순히 컴포넌트 재평가에서 그치지 않고 전체 함수를 재실행하고 이를 통해 코드를 전부 리빌드 한다.
이 과정에서 하위 컴포넌트의 불필요한 재실행을 막기 위해 React.memo
를 배웠으며, 이를 통해 리액트에게 props가 실제로 변경되었을 경우에만 컴포넌트 함수를 재실행하고 그것이 아니라면 함수를 재실행 하지 않도록 할 수 있다.
컴포넌트 재평가는 컴포넌트 함수 전체 재실행을 의미 한다는 점을 다시 한번 말하며, 함수 객체를 props를 통해 컴포넌트에 전달 할 때, 객체는 참조 값이고 React.memo
가 내부적으로 실행하는 등호를 통한 비교는 원시값에 대해서 통용되지 않기 때문에 React.memo
의 도움을 얻을 수 없다.
이를 해결하기 위해 useCallback
을 사용한다.
이 useCallback
을 통해 리액트에게 이 함수를 저장하고, 이를 둘러싼 함수가 재실행 되어도 특정 의존성이 변경 되는 것이 아니라면 기존의 함수를 반환하도록 지시 할 수 있다.