When updated React component
- state 변경될때
- props 변경될때
- forceUpdate 호출할때
0. 최적화 하기전
- useCallback, useMemo, React.memo를 사용하기전!
profiling 필수
- 무조건 Memorization 하게 된다면, 더 느려질수도 있다..
- 불필요한 연산과정
- Props 비교 - React.memo
- Dependency 비교 - useCallback, useMemo
- 불필요한 메모리 공간 차지
- useCallback - 함수
- useMemo - 값
- 불필요한 연산과정
- 언제 최적화 해야 하나?
- 같은 props로 렌더링이 자주 일어나는 컴포넌트(로그인 버튼, Polling부분)
1. 함수를 props 넘길때, useCallback이 안되면?
State Change
버튼을 클릭마다,Outer
,Inner
컴포넌트 렌더링
const Inner = ({ number, fn }) => {
console.log('Inner')
return <div>Inner {number}</div>
}
const Outer = ({ number }) => {
console.log('Outer')
const handleConsole = () => {
console.log('handleConsole')
}
return (
<div>
Outer {number}
<Inner number={'1'} fn={handleConsole} />
</div>
)
}
const App = () => {
const [number, setNumber] = useState(0)
const handleClick = () => {
setNumber(number + 1)
}
return (
<>
<button onClick={handleClick}>State Change</button>
<Outer number={number} />
</>
)
}
2. Inner는 왜 호출되지? (number랑 fn은 언제나 같은데)
- 문제점 : 렌더링 할때마다, handleConsole 함수를 새롭게 만들기 때문
- 해결책 : useCallback을 이용하자**(함수 인스턴스를 유지시켜주자)**
const Outer = ({ number }) => {
console.log('Outer')
const handleConsole = useCallback(() => {
console.log('handleConsole')
}, [])
return (
<div>
Outer {number}
<Inner number={'1'} fn={handleConsole} />
</div>
)
}
3. useCallback을 했는데도 왜 호출되지?
- 문제점 : Inner에서 props가 reference 타입이라서..(primitive 타입은 가능)
- 해결책 : React.memo를 활용하자
const Inner = React.memo(({ number, fn }) => {
console.log('Inner')
return <div>Inner {number}</div>
})
4. React.memo를 쓰면 무조건 되나?
- 문제점 : prop가 reference Type이면 안된다
- React.memo는 shallowly compare
- 기본형(number, string, undefined, bool)은 가능
- Symbol은 불가(CRA가 Bigint는 미지원)
// 렌더링 하지 않는다.
<div>
Outer {number}
<Inner number={0} str={''} und={undefined} bol={false} fn={handleConsole}/>
</div>
// 항상 렌더링
<div>
Outer {number}
<Inner symbol={Symbol(1} fn={handleConsole}/>
</div>
5. 나는 특정 props가 바뀔때만 바꾸고싶은데?
- 해결책 : React.memo의 callback을 이용하자
- 콜백의 인자에는 이전 props와 새로운 props가 들어와서 비교한다.
- callback의 결과값이 true면 렌더링 x / false면 리렌더링
// Outer
;<div>
Outer {number}
<Inner number={number} foo={{ number: 1 }} fn={handleConsole} />
</div>
const Inner = React.memo(
({ number, fn, foo }) => {
console.log('Inner')
return <div>Inner {number}</div>
},
(prev, next) => {
return prev.foo.number === next.foo.number
}
)
6. 그럼 useMemo는 ?
- useCallback는 함수를 리턴, useMemo는 값을 리턴
- useMemo를 활용하면, Object, Array, Symbol 참조타입도 가능
const Inner = React.memo(({ number, foo }) => {
console.log('Inner')
return <div>Inner {number} </div>
})
const Outer = ({ number }) => {
console.log('Outer')
const memoNumber = useMemo(() => ({ number: 1 }), []) // 렌더링 x
// const memoNumber = {number:1} // Inner 렌더링
return (
<div>
Outer {number}
<Inner number={0} foo={memoNumber} />
</div>
)
}
7. useMemo가 값이면, React.memo가 필요없는가 아냐?
- 아니다.... useMemo 또한 React.memo를 해줘야지 렌더링 안된다.
- useCallBack을 이용해서 함수 인스턴스를 유지하는 것처럼
- useMemo를 이용해서 reference type을 유지
// 렌더링 됨
const Inner = ({ number, foo }) => {
console.log('Inner')
return <div>Inner {number} </div>
}
// 렌더링 안됨
const Inner = React.memo(({ number, foo }) => {
console.log('Inner')
return <div>Inner {number} </div>
})
const Outer = ({ number }) => {
console.log('Outer')
const memoNumber = useMemo(() => ({ number: 1 }), [])
// const memoNumber = {number:1}
return (
<div>
Outer {number}
<Inner number={0} foo={memoNumber} />
</div>
)
}
8. 결론
- Props가 함수나 참조형 타입일 경우, 리렌더링 한다.
- useCallback, useMemo를 사용해도 리렌더링 한다.
- React.memo는 기본적으로 Prop 기본형 중 몇가지의 타입(number, string, undefined, bool)은 비교해서 렌더링을 막아준다
- 단, Symbol, BigInt( CRA = 미지원), 참조형 타입은 비교할 수 없다
- 함수는 useCallback, 값(참조타입, Symbol, BigInt)은 useMemo를 활용하여 Props로 넘겨주고, React.Memo를 활용하여, 리렌더링를 방지할 수 있다.