반응형
useCallback이란?
- React에서 DOM을 재렌더링 할 때 function들을 새로 할당할 것인지, 이전에 생성 된 function을 재활용 할 것인지 의사결정을 할수 있게 함
함수를 굳이 재할당 하는 이유가 있습니까?
import { useCallback, useState } from "react";
function MemoComponent({num,str,componentObj}:{num:number,str:string,componentObj:{name:string}}){
// 웹 디버깅 Memory 탭에서 확인 시 스냅샷 사이에 callbackHadndleCLick은 재선언되지 않음
const callbackHandleClick=useCallback(function callbackHandleClick(){
console.log("num::",num);
console.log("str::",str);
},[num])
const callbackNothingStatus=useCallback(function callbackHandleClick(){
console.log("num::",num);
console.log("str::",str);
console.log("componentObj::",componentObj);
},[])
const callbackAllStatus=useCallback(function callbackAllStatus(){
console.log("num::",num);
console.log("str::",str);
console.log("componentObj::",componentObj);
},[num,str,componentObj])
// 웹 디버깅 Memory 탭에서 확인 시 스냅샷 사이에 handleClick은 재선언됨
const handleClick=()=>{
console.log(num);
}
return (
<>
<button onClick={callbackHandleClick}>callbackHandleClick</button>
<button onClick={callbackNothingStatus}>callbackNothingStatus</button>
<button onClick={callbackAllStatus}>callbackAllStatus</button>
<button onClick={handleClick}>handleClick</button>
<h1>{num}</h1>
<h1>{str}</h1>
<h1>{componentObj.name}</h1>
</>
)
}
export default function WhyUse_UseCallbackExam(){
const [toggle,setToggle] = useState(false)
const [str,setStr] = useState('test')
const [num,setNum] = useState(0)
const [obj,setObj] = useState({name:"initaialValue"})
function handleToggle(){
setToggle((prev:boolean)=>!prev)
}
function handleClickStr(){
setStr("modified_str");
}
function handleClickNum(){
setNum(9999)
}
function handleAllSave(){
setNum(9999)
setStr("modified_str");
}
function handleObjSave(){
setObj({name:"modifiedValue"})
}
return(
<>
<button onClick={handleToggle}>toggle</button>
<button onClick={handleClickStr}>strClick</button>
<button onClick={handleClickNum}>numClick</button>
<button onClick={handleAllSave}>AllClick</button>
<button onClick={handleObjSave}>objClick</button>
<MemoComponent num={num} str={str} componentObj={obj}></MemoComponent>
</>
)
}
callbackNothingStatus든 callbackHandleClick든 MemoComponent에서 주는 참조형 타입 obj를
의존한다는 것은 obj의 메모리주소 (값이 변경되면 참조하는 메모리주소를 바라보고있는 값이 변경됨)는 변경되지 않음으로 예상
Case 1 (예상)
- 최초 렌더링
- componentObj의 메모리주소는 다음과같음
- componentObj의 메모리주소는 다음과같음
- handleObjSave함수 실행
- obj의 name 필드 값 변경
- callbackAllStatus 이벤트 실행
- componentObj의 데이터 값은 변경된 modifiedValue로 출력됨
- 해당 시점에 componentObj의 메모리 주소는 초기 @1679799 로 동일함(참조타입 변수이니 데이터 값만 변경됨)
Case 1(실제)
- 최초 렌더링
- componentObj의 메모리주소는 다음과같음
- componentObj의 메모리주소는 다음과같음
- handleObjSave 함수 실행
- obj의 name 필드 값 변경
- callbackAllStatus 이벤트 실행
- componentObj의 데이터 값은 변경된 modifiedValue로 출력됨
- 해당 시점에 componentObj의 메모리 주소는 초기@1679799와 동일하지 않음
왜?
React가 해당 방식으로 동작하는 이유
React는 컴포넌트의 상태와 렌더링을 관리하기 위해 불변성(Immutability) 원칙을 채택하고 있습니다. 이는 성능 최적화와 React의 기본적인 동작 원리를 일관되게 유지하기 위함입니다.
1. React의 렌더링 모델
- React는 상태(state)나 props가 변경되었을 때 컴포넌트를 다시 렌더링합니다.
- 이 과정에서 React는 변경된 부분을 효율적으로 감지하고 DOM 업데이트를 최소화하기 위해 Virtual DOM을 사용합니다.
- 참조형 데이터를 직접 수정하면 React는 변경 여부를 알 수 없으므로, 불변성을 강제하여 변경된 상태를 명확히 구분합니다.
2. 불변성을 유지하는 이유
- 변경 감지의 명확성
객체를 직접 수정하면, React가 이를 추적하는 데 어려움이 있습니다. 하지만 새로운 객체를 생성하면 참조가 변경되기 때문에 변경 여부를 쉽게 알 수 있습니다. 객체를 직접 수정하면 React가 변경을 감지하지 못함
const obj = { count: 1 }; obj.count = 2;
참조는 그대로이므로 React는 변경 여부를 알 수 없음
새 객체를 생성하면 참조가 변경되므로 React가 감지 가능 const newObj = { ...obj, count: 2 };- 성능 최적화
불변성을 유지하면 React는 얕은 비교(shallow comparison)만으로도 변경 여부를 확인할 수 있습니다. 이는 Virtual DOM 비교 작업을 더 빠르게 만들어 React의 성능을 최적화합니다.
3. 클로저와 React의 상태 관리
- React 컴포넌트 안에서 클로저를 사용할 경우, 클로저는 생성 당시의 변수 스코프를 "캡처"합니다. 하지만, React의 상태 업데이트는 클로저에 영향을 미치지 않습니다.
- React는 상태나 props가 변경될 때 기존의 값을 복사한 새로운 객체를 생성하고, 이 새로운 객체를 렌더링에 반영합니다. 따라서 클로저가 이전 값을 참조하더라도, React는 새로운 값을 기반으로 렌더링합니다.
4. 결론
React는 불변성을 유지하면서 변경 여부를 쉽게 감지하고, 이를 통해 성능 최적화를 이루기 위해 의도적으로 이러한 방식을 채택합니다. 비록 참조형 자료형의 변경이 직접적으로 반영되지 않는 것처럼 보일 수 있지만, 이는 React의 선언적 UI와 일관성을 유지하는 데 필수적입니다.
React의 이러한 설계 철학은 초기 학습 시 다소 직관적이지 않게 보일 수 있으나, 더 큰 규모의 애플리케이션에서 유지보수성과 성능 측면에서 매우 효과적입니다.
반응형