을 보고 정리한 글입니다.
React 에는 memoization 이라는 개념을 활용한다.
memoization 이란 특정 함수가 동작하고 반환된 그 결과물을 기억하고 있는 것이다.
function sum(a, b) {
return a + b;
}
위의 함수에 memoization 을 적용하면,
처음 누군가 sum(1, 2)를 호출했다면 sum을 a: 1, b: 2와 호출했고, 3이 반환되었다 라는 정보를 기록해둔다.
그러고 다시 반복되어서 sum(1, 2) 를 부르면 기록된 정보를 살피고, “sum 을 1과 2와 함께 불렀으면 3이 나오지!”라고 굳이 계산 과정을 거치지 않을 수 있다. 물론 다른 경우에 sum(2, 3) 이런 호출이 생기면 이 함수는 다시 돌아갈 것이다.
출처: https://medium.com/hcleedev/web-%EC%B5%9C%EC%A0%81%ED%99%94%EC%99%80-react-memo-usememo-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-4324a237a039
정리하자면, 어떠한 함수를 어떤 매개변수와 함께 불렀는지를 기억하는 것이라 보면 된다.
React.memo
어떤 컴포넌트를 어떤 props 와 함께 불렀는지 기록하고 기억하는 것.
function App() {
const [state, setState] = useState<number>(0);
return (
<>
<p>state</p>
<button onClick={() => setState(state + 1)}>click</button>
<MemoTest str={"안녕!"} />
</>
);
}
...
interface Props {
str: string;
}
const MemoTest = ({str}: Props) => {
return <div>{str}</div>;
};
state 가 변경되면 그 하위 컴포넌트인 MemoTest 도 리렌더링 된다.
이때, React.memo 로 감싸면 상황은 달라진다.
function App() {
const [state, setState] = useState<number>(0);
return (
<>
<p>state</p>
<button onClick={() => setState(state + 1)}>click</button>
<MemoTest str={"안녕!"} />
</>
);
}
...
interface Props {
str: string;
}
const MemoTest = ({str}: Props) => {
return <div>{str}</div>;
};
export default React.memo(MemoTest);
이경우, state 가 변경되더라도 MemoTest 는 리렌더링 되지 않는다. "str: 안녕" 이라는 값을 계속 props 로 받고있기 때문.
React.memo 는 같은 props 로 리렌더링하는 경우가 많을 때 사용한다.
function App() {
const [state, setState] = useState<number>(0);
const handleClick = () => console.log('click');
return (
<>
<p>state</p>
<button onClick={() => setState(state + 1)}>click</button>
<MemoTest handleClick={handleClick} />
</>
);
}
...
interface Props {
handleClick: () => void;
}
const MemoTest = ({handleClick}: Props) => {
return <div onClick={handleClick}}>눌러</div>;
};
export default React.memo(MemoTest);
하지만 위의경우 조심해야한다.
handleClick 은 원시타입이 아닌 함수타입이기 때문에, 리렌더링 될 때 마다 새로운 주소에 할당된다.
즉 MemoTest 가 해당 함수타입을 받게되면, 이전처럼 계속 리렌더링이 일어난다.
useMemo
복잡한 계산결과값을 memoization 할 수 있는 Hook 이다.
function App() {
const items = useState([]);
const convertedItems = items.map(item => {
...item,
additionalData: somethingHardCalc(item)
}
...
}
App 이 리렌더링 될 때 마다 convertedItems 는 불필요한 계산을 계속 하게된다.
function App() {
const items = useState([]);
const convertedItems = useMemo(
items.map(item => {
...item,
additionalData: somethingHardCalc(item)
}, [items]
)
...
}
단, useMemo 를 사용하게되면, item 이 바뀔때 만 계산을 하게된다.
useMemo 라는 함수와, 그 프로퍼티인 items 값이 뭐가 들어오는지 확인하고, 처음보는 값이면 첫 번째 인자의 함수를 구동해 그 값을 기록.
이전에 본 값이면 계산은 생략하고 전에 기록해둔 값을 돌려주는 방식으로 복잡한 계산을 줄일 수 있음.
- React.memo의 경우에는 컴포넌트를 받아 컴포넌트를 반환
- props 를 검사
- useMemo의 경우에는 값을 계산하는 과정을 최적화해 값을 반환
- dept 를 검사
useEffect
위의 useMemo 예시를 useEffect 로도 가능하다.
const [convertedItems, setConvertedItems] = useState([]);
useEffect(() => {
...계산과정...
setConvertedItems(result);
}, [items]);
useMemo 의 경우에는 랜더되는 동안 계산
useEffect 의 경우에는 랜더된 후 함수를 호출해, 다시 setConvertedItems 를 부르고, 그로 인해 한 번의 리랜더링이 더 발생할 수 있다.
즉 useMemo 는 렌더 중에, useEffect 는 렌더되고 나서 실행된다.
물론, useEffect 가 더 효과적인 경우도 있다.
useMemo 가 만약 오래 걸리는 작업을 많이 갖고 있다면, 랜더링 과정 중에 시간을 좀 오래 잡게 된다.
이때 사용자 입장에서는 화면 구성이 좀 느려진다고 생각할 수 있다.
하지만 useEffect 는 랜더링 후 돌아가므로 사용자 입장에서는 화면이 빨리 구성된다고 느낄 수는 있다.
비동기 작업 또한 마찬가지다.
useMemo 는 랜더링 작업 중에 돌아가기 때문에, 그때 비동기 작업의 결과값이 적절히 반영되는 것을 기대하는 것은 힘들다.
비동기 작업의 결과값은 side effect로서 컴포넌트에 영향을 미친다. 렌더링이 된 후 컴포넌트를 조작하다가 생기는 side effect 들은 useEffect 에서 관리할 일이라고 한다.
'🌳Frontend > react' 카테고리의 다른 글
[React] 렌더링과 컴포넌트 렌더링 순서 (0) | 2023.04.20 |
---|---|
Stop Using useMemo Now (0) | 2023.04.18 |
React 에서 주의해야하는 Event 와 addEventListener (0) | 2023.03.24 |
[SWR] SWR 동작 시나리오 (0) | 2023.03.22 |
[SWR] useSWR 장점 (0) | 2023.03.22 |