오늘은 최적화와 관련이 있는 두 가지에 대해, 어떻게 사용하는지, 언제 사용하는지에 대해 포스팅해보려고 합니다!
React.memo는 컴포넌트의 불필요한 리렌더링을 막기 위해 사용합니다.
👇 App.js
import React from 'react';
import DemoOutput from './components/Demo/DemoOutput';
import Button from './components/UI/Button/Button';
import './App.css';
function App() {
const [showParagraph, setShowParagraph] = useState(false);
const toggleParagraphHandler = () => {
setShowParagraph((prevState) => {
return !prevState;
});
};
return (
<div className='app'>
<h1>Hi there!</h1>
<DemoOutput show={false} />
<Button onClick={toggleParagraphHandler}>Toggle Paragraph!</Button>
</div>
);
}
export default App;
👇 DemoOutput.js
import React from 'react';
import MyParagraph from './MyParagraph';
const DemoOutput = (props) => {
return <p>{props.show ? 'This is new!' : ''}</p>;
};
export default DemoOutput;
위와같은 코드가 있습니다.
Toggle Paragraph!
버튼을 누르면 showParagraph State가 변경되고, App과 그 자식 컴포넌트들이 재평가 되는 코드입니다.
그러나 위 코드에서 재평가를 굳이 하지 않아도 되는 부분이 있습니다.
<DemoOutput show={false} />
바로 이 부분입니다. 위 컴포넌트의 props로 넘어가는 show는 false라는 값으로 고정이 되어있습니다.
이런 상황에서 DemoOutput 컴포넌트를 재평가하고 리렌더링하는 일은 불필요하다고 생각할 수 있습니다.
이럴 때 React.memo를 사용합니다.
import React from 'react';
const DemoOutput = (props) => {
console.log('DemoOutput RUNNING');
return <p>{props.show ? 'This is new!' : ''}</p>;
};
// props의 변화가 없을 경우 이 컴포넌트와 그 자식 컴포넌트는 재평가를 하지 않습니다. (React.memo)
export default React.memo(DemoOutput);
위 처럼 React.memo를 적용하게되면 App.js 에서 버튼을 눌러서 state를 바꿔도, DemoOutput 컴포넌트와 그 자식컴포넌트는 리렌더링되지 않게됩니다. (show={false}인 상태 한해서)
그러면 모든 컴포넌트를 React.memo로 감싸면 만사 OK일까요..?
-> 그런건 또 아닙니다. React.memo는 부모 컴포넌트를 매 번 재평가할 때마다 컴포넌트의 변화가 있거나 props의 값이 변화할 수 있는 경우라면 크게 의미를 갖지 못합니다. -> 컴포넌트의 리렌더링이 어떻게든 필요하기 때문.
React.memo를 사용하는것도 어느정도 자원이 드는걸로 알고 있어서, 무분별하게 사용하면 오히려 성능에 안좋은 영향을 끼칠것입니다!
useCallBack : 우리가 선택한 함수를 리액트의 내부 저장 공간에 저장해서 함수 객체가 실행될 때마다 이를 재사용 할 수 있게 합니다.
useCallback은 React.memo를 적용한 컴포넌트와도 연관이 있을 수 있습니다.
특히 props로 함수를 넘겨주는 경우인데요.
함수같은경우 State가 바뀌고 컴포넌트가 재평가 될 때 마다 함수가 매번 새로 재생성되는데요.
그래서 React.memo를 적용해도, 다시 리렌더링 되는것을 확인할 수 있습니다.
이 때 useCallback
을 사용하여, 항상 같은 함수를 참조하게 하여 이러한 현상을 방지할 수 있습니다.
👇 App.js
import React, { useState, useCallback } from 'react';
import './App.css';
import Button from './components/UI/Button/Button';
import DemoOutput from './components/Demo/DemoOutput';
function App() {
const [showParagraph, setShowParagraph] = useState(false);
const toggleParagraphHandler = () => {
setShowParagraph((prevState) => {
return !prevState;
});
};
return (
<div className='app'>
<h1>Hi there!</h1>
<DemoOutput show={showParagraph} />
<Button onClick={toggleParagraphHandler}>Toggle Paragraph!</Button>
</div>
);
}
export default App;
👇 Button.js
import React from 'react';
import classes from './Button.module.css';
const Button = (props) => {
console.log('BUTTON RUNNING');
return (
<button
type={props.type || 'button'}
className={`${classes.button} ${props.className}`}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</button>
);
};
export default React.memo(Button);
위 코드에서 버튼을 눌렀을 때 React.memo 로 Button 컴포넌트를 래핑했음에도 불구하고
콘솔에 "BUTTON RUNNING"
라는 문구가 버튼을 누를때마다 계속 출력되는 것을 확인할 수 있습니다.
이게 의미하는건 계속 리렌더링을 하고있다는 뜻이 되는거죠.
React.memo를 사용하여 리렌더링을 방지하려고 하는데 왜 위 코드상에서는 의도하는대로 동작을 하지 않는것일까요?
그 이유는 props로 전달되는 toggleParagraphHandler
함수에 원인이 있습니다.
App.js에서 State가 변경될 때 마다 App 컴포넌트와 그 내부의 모든 요소들은 재평가됩니다.
그러면 함수도 재생성되므로 Button 컴포넌트 입장에서는 props가 변경됬다고 인지를 하게됩니다.
그렇기때문에 React.memo로 감싸도 props가 변화했다고 인지를 했기때문에 다시 리렌더링 하게 되는것이죠.
이 떄 useCallback 훅을 사용하면 됩니다.
👇 App.js
import React, { useState, useCallback } from 'react';
import './App.css';
import Button from './components/UI/Button/Button';
import DemoOutput from './components/Demo/DemoOutput';
function App() {
const [showParagraph, setShowParagraph] = useState(false);
const toggleParagraphHandler = useCallback(() => {
setShowParagraph((prevState) => {
return !prevState;
});
}, []);
return (
<div className='app'>
<h1>Hi there!</h1>
<DemoOutput show={showParagraph} />
<Button onClick={toggleParagraphHandler}>Toggle Paragraph!</Button>
</div>
);
}
export default App;
위 처럼 재사용하고 싶은 함수를 useCallback 훅으로 감싸주면 됩니다./
useEffect 처럼 useCallback 함수로 의존성 배열을 두번째 인자로 가집니다.
이 배열에 들어가는 값이 변경되면 이 함수도 재생성되어 리액트의 내부 공간에 저장됩니다.
참고
React 완벽 가이드 with Redux, Next.js, TypeScript - Udemy