성능 개선 (2) - memo, useMemo, useTransition, useDeferredValue

·2024년 1월 24일
0

React

목록 보기
29/30
post-thumbnail

👍 성능 개선 두 번째!

이번 포스팅에서는 지난 포스팅에 이어 프로젝트 성능 개선 시 유용한 기능 몇 가지를 담아 보려고 한다.


📝 memo

컴포넌트가 재렌더링되면 그 안에 있는 자식 컴포넌트 또한 항상 함께 재렌더링이 된다. 평소에는 큰 문제가 없겠지만, 만약 자식 컴포넌트가 렌더링 시간이 1초 넘게 걸리는 무거운 컴포넌트면 어떻게 할까? 부모 컴포넌트에 있는 버튼을 누를 때마다 1초가 지연되는 문제가 발생하고 만다. 😐

이럴 때 사용하는 것이 memo다.

사용하기

간단한 예시와 함께 불필요한 재렌더링을 막아 보자.

import { memo, useState } from 'react'

let Child = memo( function(){
  console.log('재렌더링됨')
  return <div>자식 컴포넌트</div>
})

function Cart(){ 

  let [count, setCount] = useState(0)

  return (
    <Child/>
    <button onClick={()=>{ setCount(count+1) }}> + </button>
  )
}

Child로 전송되는 props가 변하는 경우에만 재렌더링이 되는 것을 콘솔창으로 확인할 수 있다.

이렇게만 보면 memo는 세상 편리해 보이는데.. 그렇다고 막 쓰는 건 안 된다. memo로 감싼 컴포넌트는 불필요한 재렌더링을 막기 위해 기존 props와 바뀐 props를 비교하는 연산이 추가로 진행되는데, 만약 props가 크고 복잡하다면 이 자체로도 큰 부담이 될 수 있다.

따라서 결론 : "꼭 필요한 곳에만 사용하자" 😶


📋 useMemo

위 memo와 유사한 useMemo라는 문법도 있다. 이것은 쉽게 말하자면 useEffect와 유사한 용도이다. 컴포넌트 로드 시 1회만 실행하고 싶은 코드가 있다면 useMemo에 담으면 된다.

사용하기

import { useMemo, useState } from 'react';

function 함수(){
    return 반복문 10억 번 돌린 결과
}

...

function Cart(){

    let result = useMemo(()=>{return 함수()}, [state])
    /* 컴포넌트 렌더링 시 1회만 실행해 줌 */
    
    return (
    	<Child />
    	<button onClick={()=>{ setCount(count+1) }}> + </button>
  	)
...

이렇게 작성한다면 useMemo에 담긴 '반복문을 10억 번 돌리는' 함수가 컴포넌트 로드 시 1회만 실행된다. 그렇다면 재렌더링 될 때마다 동작을 하지 않으니 좀 더 성능을 개선할 수 있다! useEffect처럼 특정 state, props가 변할 때만 실행하도록 코드를 짤 수도 있다.


🧚‍♀️ useTransition

재렌더링이 느린 컴포넌트의 성능을 개선할 수 있는 useTransition이라는 훅이 존재한다. 간단한 예시로 알아보도록 하자.

사용하기

import {useState, useTransition} from 'react'

/* 데이터가 10000개 있는 array 자료 만들기 */
let a = new Array(10000).fill(0)

function App(){
  let [name, setName] = useState('')
  let [isPending, startTransition] = useTransition()
  
  return (
    <div>
      <input onChange={ (e)=>{ 
    	/* 다른 코드들보다 나중에 처리하기 */
        startTransition(()=>{
          setName(e.target.value) 
        })
      }}/>

      {
        /* array에 담긴 개수만큼 div 생성하기 */
        a.map(()=>{
          return <div>{name}</div>
        })
      }
    </div>
  )
}

의도적으로 재렌더링이 느린 컴포넌트를 만들고, useTransition을 사용해 보았다. 이렇게 작성했을 때 성능이 개선되는 이유는 startTransition() 함수로 묶은 state 변경 함수가 다른 코드들보다 나중에 처리되기 때문인데. 따라서 <input> 타이핑처럼 즉각적으로 반응해야 하는 코드가 먼저 실행되고, 사용자가 느끼는 반응 속도도 훨씬 빠르게 느껴진다.

물론 근본적인 성능 개선이라기보다는 특정 코드의 실행 시점을 뒤로 옮겨 주는 것일 뿐이다. 😅

isPending은 뭐지?

startTransition()으로 감싼 코드가 처리 중일 때 true로 변하는 변수

{
  isPending ? "로딩 중..💭" :
  a.map(()=>{
    return <div>{name}</div>
  })
} 

이 변수를 활용하여 이런 식으로 로딩 화면을 만들어 보는 것도 가능하다!


🧚‍♂️ useDeferredValue

useDeferredValue는 startTransition()과 용도가 같다. 그러나 useDeferredValue는 state 또는 변수 하나를 집어 넣을 수 있고, 이 state나 변수에 변동사항이 생기면 나중에 처리해 준다.

사용하기

import {useState, useTransition, useDeferredValue} from 'react'

let a = new Array(10000).fill(0)

function App(){
  let [name, setName] = useState('')
  
  /* state에 변동사항이 생겼을 때 나중에 처리해 줌 */
  /* 처리 결과를 state1에 저장함 */
  let state1 = useDeferredValue(name)
  
  return (
    <div>
      <input onChange={ (e)=>{ 
          setName(e.target.value) 
      }}/>

      {
        a.map(()=>{
          return <div>{state1}</div>
        })
      }
    </div>
  )
}

이렇게 작성하면 위와 같은 기능을 개발할 수 있다.

profile
풀스택 개발자 기록집 📁

0개의 댓글