useCallback으로 이슈 해결하기

VoC 중 이런 이슈가 접수되었다.
앱 다크모드 설정
화면방향 변경 (가로모드 > 세로모드)
다크모드 풀림
원인
현재 최상위 컴포넌트의 useEffect에는 아래와 같이 Dimension에 변화가 있을 경우 width, height를 업데이트하는 로직으로 구현되어있다.
즉, 아이패드의 화면 방향 전환과 같이 Dimension 변화가 일어난 경우에는 이 로직이 실행되게 된다. 참고로 context는 전역 state로, theme도 이 context 데이터 중 하나이다.
useEffect(() => {
Dimensions.addEventListener('change', () => {
if (!inBackground.current) {
const {width, height} = Dimensions.get('window');
resetDimensions();
setContext({...context, width, height});
}
});
}, [])
이 로직에선 변경된 width, height를 업데이트하기 위해 기존 context 및 새로운 width, height값을 넣고 setContext를 실행하지만! 의존성배열에 아무 값도 없는 useEffect에 있는 Dimensions.addEventListner는, 변경된 theme이 업데이트되지 않은 상태이기 때문에 화면방향 전환 전의 theme이 다시 setContext로 들어가게 된다.
해결
addEventListner 이벤트핸들러 내 함수의 추출, useCallback 으로 theme이 업데이트 될 때마다 함수를 다시 생성하도록 함
const handleChangeDimension = useCallback(() => {
if (!inBackground.current) {
const {width, height} = Dimensions.get('window');
resetDimensions();
setContext({...context, width, height});
}
}, [context.theme]);
이벤트 핸들러를 handleDimension 함수 변화에 의존하는 useEffect내로 이동
useEffect(() => {
Dimensions.addEventListener('change', handleChangeDimension);
return () => {
Dimensions.removeEventListener('change', handleChangeDimension);
};
}, [handleChangeDimension]);
더 자세히 알아보는 useCallback
원하는 타이밍(두번째인자 값 변화)에 첫번째 인자로 들어간 함수를 새로 만들어 실행시키기 위한 것 (특정 함수를 재사용)
의존성배열 (두번째인자)에 있는 값이 변화되었을 때,
첫번째 인자 함수를 새로 생성해 반환
메모이제이션된 함수를 반환
자주 렌더링되는 컴포넌트에서 함수 최적화하고, 불필요한 함수 재생성 방지 시 사용
→ 메모이제이션이란? 기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법
형태
const function = useCallback(함수, [인자])
첫번째 인자로 넘어온 함수를, 두번째 인자가 변경될 때까지 저장(재사용)
두번째 인자 외 다른 state가 변경될 때에는 함수를 재생성하지 않는 듯함
어떤 A, B함수는 동일한 형태로 동일한 값을 반환한다 하더라도 참조가 다르기 때문에 A, B는 같다고 할 수 없음. 특정 state 업데이트로 인한 컴포넌트 리렌더링 시 함수가 다시 그려질 때 해당 함수를 사용하는 하위 컴포넌트가 리렌더링되는 것 역시 그와 같은 이유 때문! 즉, 함수는 값이 아닌 참조로 비교되기 때문임
추가로 자식컴포넌트에 함수를 props으로 줄때는 반드시 useCallback을 사용하여 리렌더링이 안되도록 해야 한다.