[React]리액트 최적화 - useCallback, useMemo, React.memo
[ 메모이제이션 Memoization ]
메모이제이션(Memoization)이란 처음 진행된 연산은 메모리에 기록해 두고, 이미 진행했던 동일한 연산이라면 기록한 값을 가져와 중복 계산을 제거하여 빠른 처리를 가능하게 하는 기술입니다.
리액트의 useCallback, useMemo, React.memo는 모두 메모이제이션을 기반으로 동작합니다.
[ useMemo - 값 재사용]
import { useMemo } from "react";
const memoizationValue = useMemo(function, deps);
useMemo는 값(객체)를 메모이제이션 해줍니다.
useMemo는 두 번째 인수로 들어간 deps가 변한다면, 첫 번째 인수로 들어간 함수를 실행하고, 그 함수의 반환 값을 반환합니다.
이때 deps는 배열 형태의 함수 실행 조건으로 dependecy 배열입니다.
특정 상황에서만 동작되어야 하는 함수가, 컴포넌트의 렌더링 조건에 따라 지속적으로 함수가 실행되는 경우 비효율적이게 됩니다.
이러한 경우 useMemo를 사용하여 동작조건을 설정하고, 그에 맞는 동작을 설정해주면 됩니다.
다음은 useMemo 예제입니다. button의 style props를 통해 스타일을 적용한다고 가정했을 때, useMemo를 사용하지 않고 일반 객체를 넣으면 리렌더링시마다 새로운 객체가 생성되어 button 또한 계속 리렌더링 됩니다.
button의 스타일은 계속 동일한데도 불구하고 불필요한 리렌더링은 비효율적이겠죠?
이때 useMemo를 사용하여 객체를 메모이제이션 해줌으로서 동일한 참조를 할 수 있도록 합니다.
처음 렌더링 되었을 때 ButtonStyle 객체가 생성되고 이후에는 단지 참조만 할 뿐, 생성되지 않는 것을 콘솔창을 통해 확인할 수 있습니다.
import { useCallback, useMemo, useState } from 'react';
function App() {
const [num, setNum] = useState(0);
const onClick = (e) => {
if(e.target.id === 'plus'){
setNum(num+1);
}else{
setNum(num-1);
}
};
const ButtonStyle = useMemo(()=>{
console.log('count...')
return {backgroundColor: 'skyblue'}
}, []);
return (
<div>
<button style={ButtonStyle} type="button" id="plus" onClick={onClick}>+</button>
<span> {num} </span>
<button style={ButtonStyle} type="button" id="minus" onClick={onClick}>-</button>
</div>
);
}
export default App;
[ React.memo - 컴포넌트 재사용]
import React from "react";
function 컴포넌트(){
...
}
export default React.memo(컴포넌트);
또는
const 컴포넌트 = React.memo(()=>{
...
});
React.memo는 컴포넌트를 메모이제이션 해줍니다.
부모 컴포넌트로부터 넘겨받은 props가 변화하지 않았다면 메모이제이션 해둔 렌더링 결과를 가져옵니다. 메모이제이션 해둔 컴포넌트를 재사용함으로써 렌더링 시 가상 DOM에서 달라진 부분을 확인하지 않아 성능이 향상됩니다.
예를 들어 웹 페이지의 Header 부분과 Footer와 같이 페이지가 변화하여도 변화하지 않는 부분은 React.memo를 통해 컴포넌트를 메모이제이션해주면 되겠죠?
// src/App.jsx
import React from "react";
import Header form "./Header";
const Footer = React.memo(()=>(
<div style={footerStyle}>
<h1>Footer 부분</h1>
</div>
))
function App() {
return (
<>
<div style={styledBox}>
<Header />
<Contents />
<Footer />
</div>
</>
);
}
export default App;
// src/Header.jsx
import React from 'react';
const Header = () => {
const headerStyle = {
width: '100%',
height: '100px',
backgroundColor: 'skyblue',
textAlign: 'center'
}
return (
<>
<h1 style={headerStyle}>Header 부분</h1>
</>
);
};
export default React.memo(Header);
[ useCallback - 함수 재사용]
import { useCallback } from "react";
const memoizationFunction = useCallback(function, deps);
컴포넌트가 리렌더링 될 때마다 해당 컴포넌트의 함수들이 생성된다면 자원 낭비입니다.
useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용하는 Hook 입니다.
첫 번째 인수에는 콜백 함수를, 두 번째 인수에는 dependecy 배열을 전달합니다.
deps가 변한다면, 첫 번째 인수로 들어간 새로운 함수를 반환합니다. 이때 이 함수는 형태가 같더라도 아예 새로운 함수를 반환하게 됩니다.
deps가 변하지 않는다면 항상 같은 함수(객체)를 반환해 줌으로써 불필요한 리렌더링을 방지할 수 있습니다.
import { useState, useCallback } from 'react';
function App() {
const [number, setNumber] = useState(0);
const [color, setColor] = useState('black');
const onClick = useCallback( e => {
if(e.target.id === 'plus'){
setNumber(number + 1)
setColor('red')
}else {
setNumber(number - 1)
setColor('blue')
}
}, [number, color]);
return (
<div>
<button id="plus" onClick={onClick}>+</button>
<span style={{color: `${color}`}}> {number} </span>
<button id="minus" onClick={onClick}>-</button>
</div>
);
}
export default App;