리액트 18 버전 이후부터 렌더링 성능이 저하되는 컴포넌트에서 쓸 수 있는 기능이 추가되었다.
1. 일관된 batching (automatic batching)
setCount(1)
setName(2)
setValue(3) // 여기서만 1번 재렌더링 됨
state 변경 함수를 연달아서 3개 사용하면 재렌더링도 원래 3번 되어야 하지만, 리액트는 재렌더링을 마지막에 1회만 처리해 준다. 이렇게 일종의 쓸데없는 재렌더링을 방지해 주는 기능을 batching이라고 한다.
fetch().then(() => {
setCount(1) //재렌더링됨
setName(2) //재렌더링됨
})
리액트 17 버전까지는 ajax 요청, setTimeout 안에 state 변경 함수가 있는 경우에는 batching이 일어나지 않는다.
하지만 18 버전 이후부터는 state 변경 함수가 어디에 있든 일관적이게 재렌더링이 마지막 1번만 된다. 이렇게 비동기 작업 내에서도 자동으로 배칭을 적용하여 한 번의 렌더링으로 처리하는 기능 automatic batching이라고 한다.
만약 automatic batching을 원하지는 않고, state 변경 함수 실행마다 재렌더링 시키고 싶으면 flushSync라는 함수를 사용하면 된다.
2. useTransition
렌더링 시간이 매우 오래 걸리는 컴포넌트가 있다고 해 보자. 버튼 클릭, 타이핑할 때마다 그 컴포넌트를 실행해야 한다면 버튼 클릭, 타이핑 반응 속도도 느려진다.
이럴 경우에 당연히 그 컴포넌트 안의 html 개수를 줄이면 대부분의 경우 해결이 되지만, 안 되는 경우에는 useTransiton 기능을 사용하면 된다.
import {useState} from 'react'
let a = new Array(10000).fill(0)
function App(){
let [name, setName] = useState('')
return (
<div>
<input onChange={ (e)=>{ setName(e.target.value) }}/>
{
a.map(()=>{
return <div>{name}</div>
})
}
</div>
)
}
우선, 재렌더링이 느린 컴포넌트를 만들었다.
- 데이터가 10000개가 들어있는 array 자료를 하나 만들고
- 그 개수만큼 <div>를 생성,
- 그리고 유저가 타이핑할 수 있는 <input>도 생성했다.
유저가 <input>에 타이핑하면 그 글자를 <div> 1만개 안에 집어 넣어 줘야 하는데, <div> 1만개 렌더링하느라 <input>도 많은 지연 시간이 발생한다. 그래서 타이핑한 결과도 바로바로 나타나지 않는다. 이럴 때 useTransition을 사용하면 좋다.
import {useState, useTransition} from 'react'
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)
})
}}/>
{
a.map(()=>{
return <div>{name}</div>
})
}
</div>
)
}
useTransition()을 사용하면 그 자리에 [변수, 함수]가 남는다.
그중 우측에 있는 startTransition() 함수로 state 변경 함수를 묶으면 그걸 다른 코드들보다 나중에 처리해 준다.
타이핑해 보면 반응 속도가 빨리진 것을 확인할 수 있다.
그래서 <input> 타이핑 같이 즉각 반응해야 하는 걸 우선적으로 처리해 줄 수 있다.
물론 근본적인 성능 개선이라기보다 특정 코드의 실행 시점을 뒤로 옮겨주는 것일뿐이다. html이 많으면 여러 페이지로 쪼개는 것이 좋다.
isPending
- isPending은 startTransiton()으로 감싼 코드가 처리중일 때 true로 변하는 변수.
{
isPending ? "로딩중입니다." :
a.map(()=>{
return <div>{name}</div>
})
}
그래서 이런식으로 코드를 짜는 것도 가능하다.
위의 코드는 useTransiton으로 감싼 게 처기 완료되면 <div>{name}</div>가 보일 것이다.
3. useDeferredValue
startTransition()과 용도가 같다.
차이점은 state나 변수 하나를 집어 넣어서 그 변수에 변동 사항이 생기면 그것을 늦게 처리해 준다.
import {useState, useTransition, useDeferredValue} from 'react'
let a = new Array(10000).fill(0)
function App(){
let [name, setName] = useState('')
let state1 = useDeferredValue(name)
return (
<div>
<input onChange={ (e)=>{
setName(e.target.value)
}}/>
{
a.map(()=>{
return <div>{state1}</div>
})
}
</div>
)
}
이렇게 쓰면 위에서 startTransition() 함수를 사용한 것과 똑같은 결과를 볼 수 있다.
useDeferredValue 안에 state를 집어 넣으면 그 state가 변동 사항이 생겼을 때 나중에 처리해 준다. 그리고 결과는 변수(let state1)에 저장해 준다.
* 이 포스팅은 코딩애플 강의를 토대로 작성하였습니다.