[React] React hooks 정리 part.2 useMemo(최적화) feat. React.memo
React Hooks 정리 중! 이번 파트에는 리액트 최적화를 위한 useMemo를 파헤쳐보자 :)
들어가기 전
다른 hooks들이 궁금하다면 하단으로 ㄱㄱ!
useState
useEffect / useRef / useContext + Context API
🔗 useMemo(최적화) feat. React.memo가 뭘까?
동일한 값을 리턴하는 함수를 반복적으로 호출해야 한다면 맨 처음 호출 할 때 해당 값을 메모리에 저장해서 필요할때 꺼내서 사용하는 방법으로 간단히 말하면 자주 필요한 값을 맨 처음 계산할때 캐싱을해 둬서 그 값이 필요할 때 계산을 하는 것이 아니라 캐싱을 해둬서 꺼내서 사용한다는 뜻
useMemo의 개념
- 성능을 최적화할 때 사용한다.
- memo는 memorized의 약자이다.
- 첫번째 인수에는 함수, 두번째 인수에는 배열을 넣어주면 된다.
- 두번째 인수에 넣어준 배열의 값이 바뀔때만 함수가 실행된다.
- 그렇지 않다면 이전의 값을 재사용한다.
- 기존의 state의 값과 변화된 state의 값을 비교한 후 연산이 오래걸리는 함수를 실행할 지 말지 결정하기 때문에 성능 최적화에 사용된다.
1. 사용법(문법)
import { useMemo } from "react";
//연산이 오래 걸리는 어떠한 결과 값을 리턴해주는 함수
const func = () => {
return //연산이 오래 걸린 결과 값
}
const result = useMemo(() => {
return func();
}, [state]) // []배열은 dependency array로 해당 state의 값이 변경 될 때만 재연산한다.
⭐️ useRef와 useMemo의 차이점
- useRef: 일반 값을 기억
- useMemo: 연산이 오래 걸리는 복잡한 함수의 결과값을 기억
⭐️ useEffect와 useMemo의 실행 시점 차이
- useEffect: 컴포넌트 렌더링이 완료된 뒤 실행
- useMemo: 컴포넌트 렌더링이 진행 중일 때 실행
useMemo 사용 예제
//App.js
import "./App.css";
import { useState } from "react";
const hardCalculate = (number) => {
console.log("어려운 계산 시작!");
for (let i = 0; i < 999999999; i++) {} // 생각하는 시간
return number + 10000;
};
function App() {
const [hardNumber, setHardNumber] = useState(1);
const hardSum = hardCalculate(hardNumber);
return (
<div className="App">
<div>
<h3>어려운 계산기</h3>
<input
type="number"
value={hardNumber}
onChange={(e) => {
setHardNumber(parseInt(e.target.value));
}}
/>
<span> + 10000 = {hardSum}</span>
</div>
</div>
);
}
export default App;
위와 같이 작성할 경우, hardCalculate 함수 내부의 for문에 의해 return 값이 늦게 리턴되기 때문에 input의 값을 1씩 증가시킬 경우 hardSum 의 값이 화면에 1초씩 늦게 렌더링 될 것이다.
또한 hardNumber state의 값이 변경 될 때마다 hardCalculate 함수가 반복적으로 불려지게 되고 hardSum이라는 변수에 반복적으로 값을 할당해준다. 따라서 hardNumber state가 변경 될 때마다 우리의 App컴포넌트는 재렌더링이 일어날 것이다.
이제 어려운 계산기 밑에 쉬운 계산기를 추가해보자.
//App.js
import "./App.css";
import { useState } from "react";
const hardCaluculate = (number) => {
console.log("어려운 계산 시작!");
for (let i = 0; i < 999999999; i++) {} // 생각하는 시간
return number + 10000;
};
const easyCaluculate = (number) => {
console.log("짱 쉬운 계산 시작!");
return number + 1;
};
function App() {
const [hardNumber, setHardNumber] = useState(1);
const [easyNumber, setEasyNumber] = useState(1);
const hardSum = hardCaluculate(hardNumber);
const easySum = easyCaluculate(easyNumber);
return (
<div className="App">
<div>
<h3>어려운 계산기</h3>
<input
type="number"
value={hardNumber}
onChange={(e) => {
setHardNumber(parseInt(e.target.value));
}}
/>
<span> + 10000 = {hardSum}</span>
</div>
<div>
<h3>짱 쉬운 계산기</h3>
<input
type="number"
value={easyNumber}
onChange={(e) => {
setEasyNumber(parseInt(e.target.value));
}}
/>
<span> + 1 = {easySum}</span>
</div>
</div>
);
}
export default App;
위와 같이 작성 후 쉬운계산기의 카운트를 올려보면 어려운 계산기와는 다르게 빠르게 계산이 될거라 생각하지만
쉬운 계산기의 easyNumber state가 변경 될 때마다 App컴포넌트가 다시 렌더링이 되면서 변수가 초기화가 되기 때문에 hardCaluculate 함수가 다시 불려와지면서 딜레이가 생기는 것이다.
만약 쉬운 계산기가 실행 될 때 hardSum 함수를 실행시키지 않도록 하려면 useMemo를 사용하면 된다.
//App.js
import "./App.css";
import { useMemo, useState } from "react";
const hardCaluculate = (number) => {
console.log("어려운 계산 시작!");
for (let i = 0; i < 999999999; i++) {} // 생각하는 시간
return number + 10000;
};
const easyCaluculate = (number) => {
console.log("짱 쉬운 계산 시작!");
return number + 1;
};
function App() {
const [hardNumber, setHardNumber] = useState(1);
const [easyNumber, setEasyNumber] = useState(1);
//const hardSum = hardCaluculate(hardNumber);
const hardSum = useMemo(() => {
return hardCaluculate(hardNumber);
}, [hardNumber]);
const easySum = easyCaluculate(easyNumber);
return (
<div className="App">
<div>
<h3>어려운 계산기</h3>
<input
type="number"
value={hardNumber}
onChange={(e) => {
setHardNumber(parseInt(e.target.value));
}}
/>
<span> + 10000 = {hardSum}</span>
</div>
<div>
<h3>짱 쉬운 계산기</h3>
<input
type="number"
value={easyNumber}
onChange={(e) => {
setEasyNumber(parseInt(e.target.value));
}}
/>
<span> + 1 = {easySum}</span>
</div>
</div>
);
}
export default App;
쉬운 계산기를 눌렀을 경우 어려운 계산기의 hardNumber state가 변경 되지 않은 것을 확인한 useMemo hook이 hardSum함수를 실행시키지 않고 기존의 연산값을 그대로 사용한 것을 확인할 수 있고
어려운 계산기를 눌렀을 경우 hardNumber state가 변경 되었기 때문에 useMemo hook이 hardSum 함수를 실행 시킨 것을 확인 할 수 있다.
Feat. 자식 컴포넌트의 재 렌더링을 막아보자 (React.memo)
부모 컴포넌트가 재렌더링이 될 때마다, 재렌더링이 일어날 필요 없는 자식 컴포넌트까지 렌더링 되지 않도록 막아서 재렌더링이 필요한 순간에만 재렌더링이 되도록 해보자.
//App.js
import "./App.css";
import { memo, useState } from "react";
const Child = () => {
console.log("자식 컴포넌트 렌더링!");
return <div>자식녀석 나타났소.</div>;
};
function App() {
const [count, setCount] = useState(1);
return (
<div className="App">
<h1>내 자식 녀석 어디갔누?</h1>
<Child />
렌더링 증가
<input type="number" value={count} onChange={() => setCount(count + 1)} />
</div>
);
}
export default App;
위와같이 작성하면 input의 값이 증가할 때마다 count state가 변경되고 App 컴포넌트가 재렌더링이 되면서
자식컴포넌트 또한 같이 재렌더링이 일어나는 것을 확인할 수 있을 것이다.
//App.js
import "./App.css";
import { memo, useState } from "react";
const Child = memo(() => {
console.log("자식 컴포넌트 렌더링!");
return <div>자식녀석 나타났소.</div>;
});
function App() {
const [count, setCount] = useState(1);
return (
<div className="App">
<h1>내 자식 녀석 어디갔누?</h1>
<Child />
렌더링 증가
<input type="number" value={count} onChange={() => setCount(count + 1)} />
</div>
);
}
export default App;
위와같이 Child 컴포넌트에 memo를 이용해 감싸주면 부모컴포넌트가 재렌더링이 되더라도
Child컴포넌트의 렌더링 전과 후의 상태 비교 후 상태 변화가 있다면 재 렌더링을 진행하고, 없다면 재 렌더링을 하지 않기 때문에 메모리를 효율적으로 관리 할 수 있게 된다.
언제 React.memo를 사용해야 할까?
- 같은 props로 렌더링이 자주 일어나는 컴포넌트
- 부모 컴퍼넌트에 의해 하위 컴퍼넌트가 같은 props를 전달받아 리렌더링 될 때와 같은 경우
- 무겁고 비용이 큰 연산이 필요한 컴포넌트의 경우
참고자료
유튜브 별코딩 리액트 훅
'Study > react.js' 카테고리의 다른 글
[React] 리액트 차트 라이브러리 Recharts.js 적용기(React chart library Recharts.js) (0) | 2023.06.27 |
---|---|
[React] React hooks 정리 part.1 - useState, useEffect, useRef, useContext + Context API (0) | 2023.02.20 |
[React] React Portal 기술을 이용하여 Modal을 만드는 방법(실습파일 하단 첨부) (0) | 2023.02.06 |
[React] React-query 정리 (0) | 2023.02.03 |
[React] 클래스형 vs 함수형 컴포넌트 차이 (class vs function component) (1) | 2022.12.08 |