2022년 11월 11일
React
조회 : 297|3분 읽기

React usememo & usecallback

useMemo

React 가상돔의 전략은 자기 자신과 리얼돔의 차이점을 발견하면 이전 렌더링된 결과와 비교하여 리얼돔 업데이트를 결정한다.
이때 함수형 컴포넌트에서 이런 차이점을 catch하는 Trigger는 state변경이다.
리액트의 state 변경은 자식 컴퍼넌트까지 리렌더링을 시키는데 가끔 자식들은 리렌더링이 필요하지 않은 경우가 있다.
이때 memo를 사용해 이 자식들의 리렌더링을 하지 않도록 만들 수 있다.
React.memo
1. 컴포넌트에 사용은
렌더링 결과를 메모이징(memoizing)함으로 불필요한 렌더링을 줄여준다.
component의 props가 변경되지 않았다면 리렌더링하지 않도록 방지해준다.
2. 연산 결과는
memo는 컴포넌트에만 사용하는 것이 아니라 연산된 결과를 리렌더링이 일어날때 다시 연산하지 않아도 되도록 도와주는 기능이다.
자식 컴포넌트도 결국 컴포넌트 내의 연산된 결과이기 때문에 적용된다.

🔧 예시
javascript
1function Book({title, writer,price}){
2return(
3<div>
4<div>title : {title}</div>
5<div>writer : {writer}</div>
6<div>price : {price}</div>
7</div>
8)
9}
10
11export default React.memo(Book);

React.memo(Book)는 새로 메모이징된 컴퍼넌트인 MemoizedBook을 반환한다.
만약 title, writer과 같은 props의 변경이 없다면 다음 렌더링 때 메모이징된 내용을 그대로 사용하게된다.

props 동등 비교 커스터마이징

React.memo는 props혹은 props의 객체를 비교할 때 얕은 비교를 한다.
비교방식을 수정하기 위해선 아래와 같이 만들어주면 된다.
javascript
1React.memo(Component,[areEqual(prevProps, nextProps)]);
areEqual(prevProps, nextProps)함수는 두 인자(이전 props, 현재 props)가 같다면 true를 반환할 것이다.


🤔 React.memo를 사용해야할 때

같은 props로 렌더링이 자주 일어나는 컴포넌트
React.memo는 함수형 컴포넌트에 적용되어 같은 props에 같은 렌더링 결과를 제공한다.
React.memo를 사용하기 가장 좋은 케이스는 함수형 컴포넌트가 같은 props로 자주 렌더링 될거라 예상될때.

🔧 react-beautiful-dnd를 사용해서 toDo의 index하나를 옮기는데 toDo의 배열 전체가 rerendering되는 상황이다.

javascript
1import { Draggable } from "react-beautiful-dnd";
2import styled from "styled-components";
3import React from "react";
4
5const Card = styled.div`
6  border-radius: 5px;
7  padding: 5px 10px;
8  margin-bottom: 5px;
9  background-color: ${(props) => props.theme.cardColor};
10`;
11
12interface IDragabbleCrad {
13  toDo: string;
14  index: number;
15}
16
17function DragabbleCard({ toDo, index }: IDragabbleCrad) {
18  return (
19    <Draggable key={toDo} draggableId={toDo} index={index}>
20      {(magic) => (
21        <Card
22          ref={magic.innerRef}
23          {...magic.draggableProps}
24          {...magic.dragHandleProps}
25        >
26          {toDo}
27        </Card>
28      )}
29    </Draggable>
30  );
31}
32
33export default React.memo(DragabbleCard);
DragabbleCard가 변화하는 상황일때만 렌더링하기 위해서 memo를 사용한다.
이외에 API를 받아오는 등 많은 자원을 불필요한 렌더링을 하기 위해 낭비를 막기 위한 상황에서 사용한다.


❌ React.memo을 사용하지 말아야할 때

쓸모없는 props 비교
렌더링 될때 props가 다른 경우가 대부분인 component를 생각해보면 메모이제이션의 이점을 얻기 힘들다.
props가 자주 변하는 component를 React.memo()로 래핑하는 것은 좋은 방법이 아니다.


결론

React.memo은 성능 개선 도구이다.
React.memo을 통해 component의 불필요한 리렌더링을 막을 수 있지만 메모이제이션에 의존해선 안된다.
결국 memo는 브라우저의 메모리에 결과를 캐싱하는 것이기 때문에 남발하게 되면 성능 최적화가 아니라 성능 저하로 이어질 수 있다.
따라서 진짜 비싼 연산의 경우에만 그 기준을 잘 생각하며 개발자의 판단으로 사용해야하며 최적화의 도구로만 사용해야지 로직을 집어넣으며 사용하면 안된다


useCallback

메모제이션된 콜백을 반환한다.
javascript
1const memoizedCallback = useCallback(
2  () => {
3    doSomething(a, b);
4  },
5  [a, b],
6);
useCallback(fn, deps)은 useMemo(() => fn, deps)와 같다.
리렌더링(재연산)이 일어날 때 연산 시간을 줄이고 재활용 가능하기 때문에 콜백을 전달할 때 유용하다.
ex) onClick
javascript
1function Btn(){
2	const clickFn = useCallback(console.log("ds"));
3  return <div onClick={clickFn}>버튼</div>
4}
우리가 일반적으로 사용하는 이벤트에 콜백으로 ()=> fn를 주는 방식은 컴포넌트가 렌더링이 될때마다 함수를 다시 정의하기 때문에 불필요한 렌더링이 생기는 경우다.
useCallback을 이용해서 함수를 메모제이션 한다면 불필요한 함수 재정의를 막을 수 있다.
하지만 이렇게 작은 부분은 성능에 영향을 주는 경우가 없기 때문에 이런 경우는 사용하지 않아도 괜찮다.
참고