이번 포스팅에서는 지난 포스팅에 이어 프로젝트 성능 개선 시 유용한 기능 몇 가지를 담아 보려고 한다.
컴포넌트가 재렌더링되면 그 안에 있는 자식 컴포넌트 또한 항상 함께 재렌더링이 된다. 평소에는 큰 문제가 없겠지만, 만약 자식 컴포넌트가 렌더링 시간이 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가 크고 복잡하다면 이 자체로도 큰 부담이 될 수 있다.
따라서 결론 : "꼭 필요한 곳에만 사용하자" 😶
위 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
이라는 훅이 존재한다. 간단한 예시로 알아보도록 하자.
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>
타이핑처럼 즉각적으로 반응해야 하는 코드가 먼저 실행되고, 사용자가 느끼는 반응 속도도 훨씬 빠르게 느껴진다.
물론 근본적인 성능 개선이라기보다는 특정 코드의 실행 시점을 뒤로 옮겨 주는 것일 뿐이다. 😅
startTransition()
으로 감싼 코드가 처리 중일 때 true로 변하는 변수
{
isPending ? "로딩 중..💭" :
a.map(()=>{
return <div>{name}</div>
})
}
이 변수를 활용하여 이런 식으로 로딩 화면을 만들어 보는 것도 가능하다!
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>
)
}
이렇게 작성하면 위와 같은 기능을 개발할 수 있다.