본문 바로가기
React

React.memo 활용

by se0nghyun2 2024. 6. 12.

리액트는 먼저 컴포넌트를 랜더링한 뒤 이전 랜더된 결과와 비교하여 DOM 업데이트 여부를 결정한다.

만약 랜더된 결과가 다르다면 React는 DOM을 업데이트한다.

 

React.Memo

React는 해당 함수로 래팽된 컴포넌트를 랜더링 후 결과를 메모리제이션한다.

그 후 다음 랜더링 진행 시 Props가 동일하다면 React는 메모리제이션한 컴포넌트를 재사용한다.

이 때, Props 비교는 얕은 비교를 통하여 진행된다.

더보기

얕은 비교

 - 숫자, 문자열 등 원시형 타입은 값을 비교

 - 배열,객체 등 참조 자료형은 주소를비교

 

깊은 비교

 - 값을 통해 비교   


 

검색된 도서의 정렬 방식 변경 시 부모의 state 변경으로 인하여 아래와 같이 모든 하위 컴포넌트들이 재랜더링되게 된다.

그러나 정렬 방식 변경 시 도서의 데이터(제목,출판일자 등)는 변함없기에 해당 컴포넌트들은 재랜더링되지 않아도 된다.

따라서 리랜더링 범위를 줄여보고자 한다.

 

정렬 순서 변경 시 랜더링 범위 확인(개선 전)

 

 

도서 정보를 지닌 컴포넌트

interface IListTypeContentProps{
    book:IBookInfo,
    regHeart ?:(e: React.MouseEvent<HTMLButtonElement>) => void,
    rentBook ?: (e: React.MouseEvent<HTMLButtonElement>) => void
}

const ListTypeContent = ({book,regHeart,rentBook}:IListTypeContentProps) => {
    return (
        <>
            <BookImgWrapper>
                <Img 
                    src={`${process.env.PUBLIC_URL}/`+ getFilePath(book.bookImage.filePath ,book.bookImage.newFileName)} 
                />
            </BookImgWrapper>

            <BookContentsWrapper>
                <BookTitle>
                    <Link style={{ fontSize:"20px"}} to={`/book/${book.bookNo}`}> 
                        {book.bookName}
                    </Link>
                </BookTitle>
                <Info>
                    <span>{book.bookAuthor}</span>
                    <span>출판사</span>
                    <span>{book.pubDt}</span>
                </Info>
                <Info>
                    <span>{book.bookState==="RENT_AVAILABLE" ? "대출 가능" : "대출 불가" }</span>
                    <span>위치</span>
                    <span>부록없음</span>
                </Info>
                <ButtonWrapper>
                    <Button 
                        onClick={rentBook}
                        value={book.bookNo}
                        $bookState={book.bookState}
                        disabled={book.bookState==="RENT_UNAVAILABLE"? true : false}
                    >
                        대출하기
                    </Button>
                    <Button value={book.bookNo}>예약하기</Button>
                    <Button value={book.bookNo} 
                    onClick={regHeart}
                    >
                        찜하기</Button>
                </ButtonWrapper>
                <HiddenContentWrapper>
                    <TextForHidden>
                        도서정보
                    </TextForHidden>
                </HiddenContentWrapper>
            </BookContentsWrapper>
        </>
    );
};

export default ListTypeContent;

 

 

이를 최적화 하기 위하여 React.memo를 사용하여 해결하였다.

export default React.memo(ListTypeContent);

 

 

Props로 함수를 전달받는 경우 고려 사항

해당 컴포넌트는 props로 '함수'를 전달받고 있다. 함수의 경우 얕은 비교를 통하여 진행되기에 주소로 비교를 하게 된다.

만약 부모로부터 전달받은 함수가 리랜더링 때마다 재생성되면 주소가 바뀌어 memo에서 판단할 때 props가 바뀐 것으로 판단하게 된다. 따라서, 해당 함수의 주소가 변하지 않도록 useCallback을 적용해 주면 된다.

 

 

부모 컴포넌트(함수에 useCallback 적용)

const BookResults = () => {
    const [books,setBooks] = useState<IBookInfo[]>([]);
    const [showing,setShowing] = useState(false); 
   

   	...
    
    
    const {mutate:regHeartMutate} = useRegHeartBook();
    const {mutate:rentBookMutate} = useRentBook();

	...

    //useCallback 적용
    const clickedHeart = useCallback((e:React.MouseEvent<HTMLButtonElement>)=>{
        if(isLogin){
            regHeartMutate.mutate({bookNo:parseInt(e.currentTarget.value),userNo:authUserInfo.userNo});
        }else{
            setShowing(true);
        }
    },[isLogin]);

    //useCallback 적용
    const clickedRent = useCallback((e:React.MouseEvent<HTMLButtonElement>)=>{
        if(isLogin){
            const bookNo = parseInt(e.currentTarget.value);
            rentBookMutate.mutate({bookNo:bookNo,userNo:authUserInfo.userNo});
        }else{
            setShowing(true);
        }
    },[isLogin]);

    return (
        <>
            <InquriyResultWrapper>
                ...      

                <InquriyResult $gridType={gridType} $isLoading={isLoading}>
                    {
                        isLoading ? 
                            <Loading /> 
                        :
                        books?.map((book) =>{
                            return (
                                <Row key={book.bookNo} $gridType={gridType}>
                                    {
                                        gridType === GridType.ListType
                                        ?
                                        <ListTypeContent book={book} regHeart={clickedHeart} rentBook={clickedRent} />
                                        :
                                        <ImgTypeContent book={book}  regHeart={clickedHeart} />
                                    }
                                </Row>
                            )
                        })
                    }
                </InquriyResult>
            </InquriyResultWrapper>

            <LoginModal showing={showing} setShowing={setShowing} />
            <Pagination totalCount={totalCount} sizePerPage={sizePerPage} currentPage={currentPage} setCurrentPage={setCurrentPage}/>
        </>
    );
};

export default BookResults;

 

 

이를 두고 리랜더링 범위를 확인해보자.

정렬 순서 변경 시 랜더링 범위 확인(개선 후)

 

이처럼 React.memo를 활용하여 불필요한 리랜더링을 막을 수 있었다.