React 렌더링에 대한 최적화시키기 위한 도구인 React.memo, useMemo, useCallback 에 대해 정리 해보았다.
최적화, 메모이제이션
업데이트, 변경과 같은 상황이 화면에서 이루어지면 컴포넌트를 다시 센터링하고 레이아웃 및 페인팅 과정을 다시 계산하는 상황이 발생하여 React의 성능을 점검할 때는 컴포넌트 자체의 리랜더링이 불필요하게 반복되고 있지 않은지, 그리고 내부 로직이 쓸데없이 다시 만들어지거나 복잡한 계산을 반복하고 있지는 않은지 고려해야 한다. Memoization(메모이제이션)은 Caching의 일종으로 이미 계산해 본 연산 결과를 기억해두었다가 동일한 연산을 해야 할 때 다시 연산하지 않고 기억해두었던 데이터를 반환시키는 방법으로 쓸데없이 같은 계산을 반복하게 하지 않게 할 수 있다.
React.memo
React.memo는 Higher-Order Components(HOC - 고차 컴포넌트)이다.
(HOC란 컴포넌트를 인자로 받아서 새로운 컴포넌트를 return해주는 구조의 함수)
얕은 비교 ?
값에 의한 비교가 아닌 주소에 의한 비교
자바스크립트에서 객체, 함수, 배열 같은 비 원시 타입의 자료형을 비교할 때 값에 의한 비교가 아닌 주소에 의한 비교를 한다
let a = { count: 1 }; let b = { count: 1 }; if (a === b) { console.log("same"); } else { console.log("not same"); } console.log(a === b); // 결과: not same
⇒ 얕은 비교를 할 때 이전의 object값과 재 할당된 object값이 서로 다른 값으로 인식되어 props값이 변경이 안 됬어도 React.memo를 적용한 컴포넌트에 리 렌더링이 발생한다.
사용법
const MyComponent = React.memo((props) => { return (/*컴포넌트 렌더링 코드*/)} ); //or function MyComponent (props) { return (/*컴포넌트 렌더링 코드*/)} } export default React.memo(MyComponent);
useMemo
useMemo는 Memoization된 값을 return하는 hook이다. ( 랜더링 과정 중 에 발동된다 / useEffect 는 랜더링이 끝나고 나서 발동됨 )
인자로 함수와 의존 값(dependencies)을 받아 의존 인자 중 하나라도 변경되면 값을 재 계산한다. (인가자 아무것도 없으면 렌더 시 마다 항상 값을 새로 계산하여 return함)
사용법
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]); //사용예시 const geAnalysis = () => { const pickItem = data.filter((elem) => elem.pick = true).length; const totalItem = data.length - pickItem; return [pickItem, totalItem]; } const [pickItem, totalItem] = useMemo(geAnalysis(), [data.length]); return ( <div> <p>pick 수 : {pickItem}개</p> <p>nonePick 수 : {noneItem}개</p> </div> )
useCallback
useCallback은 리액트의 렌더링 성능을 위해서 제공되는 Hook이다.
컴포넌트가 렌더링 될 때마다 내부적으로 사용된 함수가 새롭게 생성되는 경우, 자식 컴포넌트에 Prop으로 새로 생성된 함수가 넘겨지게 되면 불필요한 리 렌더링이 일어날 수 있다. useCallback을 사용하면 함수가 처음 생성될 때 한 번만 생성되며, 나중에는 동일한 함수 인스턴스를 재 사용하게 된다.
사용 전 코드를 보면 ItemEdit 가 렌더링이 발생할 때마다 컴포넌트의 onSave 속성값으로 새로운 함수가 전달된다. ItemEdit 컴포넌트에서 React.memo를 사용해도 전달된 Prop이 항상 바뀌므로 불필요한 렌더링이 발생한다.
사용 후 코드에서 useCallback을 활용해 1번째 인자로 함수, 2번째 인자로 Dependencies를 전달해 전달된 의존성 인자가 바뀌지 않으면 이전에 생성한 함수가 재사용 된다.
자식컴포넌트에 함수를 props으로 줄때는 반드시 useCallback을 사용하여면 리렌더링을 방지 할 수있다.
//사용 전 import React, {useSatate} from 'react'; import {saveToServer} from './api'; import ItemEdit from './ItemEdit'; function ItemEdit(){ const [itemName, setItemName] = useState(''); const [itemPrice, setItemPrice] = useState(0); return ( <div> <p>{itemName}</p> <p>{`price : ${itemPrice}`}</p> <ItemEdit onSave={() => saveToServer(itemName, itemPrice)} setItemName={setItemName} setItemPrice={setItemPrice} /> </div> ); } //사용 후 function ItemEdit(){ const [itemName, setItemName] = useState(''); const [itemPrice, setItemPrice] = useState(0); const onSave = useCallback(() => saveToServer(itemName, itemPrice), [itemName, itemPrice]); return ( <div> <p>{itemName}</p> <p>{`price : ${itemPrice}`}</p> <ItemEdit onSave={onSave} setItemName={setItemName} setItemPrice={setItemPrice} /> </div> ); }
React.memo, useMemo, useCallback
공통점
차이점