automatic batching 기능
state 변경 함수가 쭉 작성되어 있다 하자.
state1변경()
state2변경()
state3변경()
state가 변경될 때마다 재렌더링이 일어난다고 했으니 재렌더링이 3번 일어날까?
아니다. 맨 마지막인 state3변경()에서만 재렌더링이 1회 일어난다. 이걸 batching이라 한다. 뭉쳐서 실행되기 때문이다.
예외도 있다. 리액트 17버전인 경우, ajax, setTimeout과 같은 비동기 함수 내부에서 위 코드를 작성했다고 하자. 그럼 batching이 일어나지 않고 각각 재렌더링이 일어나서 총 3번의 재렌더링이 일어난다.
하지만 리액트 18버전부터는 state가 변경되는 코드가 어디에 있던지 batching이 잘 일어나게 되었다. 그래서 비동기 함수 내부에서도 batching이 일어나서 마지막에만 실행되어 총 1번의 재렌더링이 일어난다.
useTransition: 느린 컴포넌트 성능향상가능
동작이 느린 컴포넌트를 혁신적으로 빠르게 동작시킬 수 있다. 카드 빚 돌려막기식으로 말이다.
🕹️App.js
import { useState } from 'react';
function App(){
let [name, setName] = useState('');
return(
<div className="App">
<input onChange={(e) => setName(e.target.value)} />
</div>)
}
유저가 뭘 입력하면 name에 입력한 값이 저장된다.
여기서 의도적으로 성능저하를 일으켜보자.
import { useState } from 'react';
function App(){
let [name, setName] = useState('');
return(
<div className="App">
<input onChange={(e) => setName(e.target.value)} />
{
new Array(10000).fill(0).map(() => <div>{name}</div>)
}
</div>)
}
그럼 input 아래에 div가 10000개 생긴다. 타이핑을 할 때마다 name state가 변경되고, name이 변경되면 10000개의 div 박스도 렌더링되어야 한다. 그래서 화면이 버벅이는 현상이 발생한다.
근데 유저는 조작 후에 0.2초 넘게 반응이 없으면 유저 다 떠난다.. 그래서 이 문제를 해결해보자.
솔루션1. html 10000개 지우기
원인을 없애면 젤 좋다. 근데 어쩔 수 없이 써야하는 상황이라면?
솔루션2. startTransition 쓰기
import { useState, useTransition } from 'react';
function App(){
let [name, setName] = useState('');
let [isPending, startTransition] = useTransition();
return(
<div className="App">
<input onChange={(e) =>
startTransition(() => {
setName(e.target.value)}
})/>
{
new Array(10000).fill(0).map(() => <div>{name}</div>)
}
</div>)
}
- import useTransition
- startTransition으로 문제의 state변경 감싸기
지연의 원인은 setName(e.target.value)다. 그 함수를 startTransition으로 감싸자.
그럼 성능이 아까보다 좀 나아진 걸 볼 수 있다.
앞으로 state 변경함수가 성능 저하를 일으킨다, 그러면 startTransition 함수로 감싸자.
startTransition 동작원리
먼저 왜 성능저하를 일으켰냐? 브라우저는 single-threaded기 때문에 동시 작업을 못하고 한 번에 하나의 작업만 수행할 수 있다. 그래서 타이핑을 했을 때 브라우저가 할 일은 다음과 같다.
- <input>에 name을 보여주기
- `<div> 를 10000개 만들기
그런데 startTransition 함수로 감싸주게 되면,
startTransition 안에 있는 코드들을 모두 늦게 처리해준다. 코드 시작 시점을 뒤로 늦추고, 다른 중요한 작업을 먼저 한다는 거다.
그러면
- 먼저 name을 <input>에 보여주기
- **한가할 때** <div>를 10000개 만들기
로 실행하게 된다. 우선순위가 변경되어서 성능이 향상되는 것처럼 보이는 거다.
더 명시적으로 작성하자면 이렇게 되겠다.
const [isPending, 늦게처리] = useTransition()
isPending: startTransition이 처리 중일 때 true로 변함
처리 중일 때 isPending은 true다. 이걸 이용해서 로딩중을 띄울 수도 있다.
import { useState, useTransition } from 'react';
function App(){
let [name, setName] = useState('');
let [isPending, startTransition] = useTransition();
return(
<div className="App">
<input onChange={(e) =>
startTransition(() => {
setName(e.target.value)}
})/>
{
isPending ? '로딩중' : new Array(10000).fill(0).map(() => <div>{name}</div>) // ⬅️
}
</div>)
}
useDeferredValue써도 느린 컴포넌트 성능향상기능
위 코드에서 이어서 보자. useDeferredValue를 써도 느린 컴포넌트의 성능을 향상시킬 수 있다.
얘도 기능이 같다. 대신 얘는 함수를 감싸지 않고, state나 props를 넣으면 다시 변수롤 퉤-뱉어준다. 뱉어져서 나온 이 변수는 변동사항이 생기면 늦게 처리해준다.
import { useState, useTransition } from 'react';
function App(){
let [name, setName] = useState('');
let state = useDeferredValue(name);
return(
<div className="App">
<input onChange={(e) => setName(e.target.value)} )/>
{
new Array(10000).fill(0).map(() => <div>{state}</div>)
}
</div>)
}
name이라는 state가 변할 때마다, name을 사용하는 것들이 나중에 처리가 되므로 중요한 작업들을 먼저 처리할 수 있다. 그래서 더 빠른 느낌의 웹사이트를 만들 수 있다.
'Front-End: Web > React.js' 카테고리의 다른 글
특정 동작 시, useQuery하기 (0) | 2023.04.09 |
---|---|
[React] 엘리먼트 리스트에 key가 왜 필요해? (0) | 2022.12.06 |
[코딩애플] 성능개선 2: 재렌더링 막는 memo, useMemo (0) | 2022.11.09 |
[코딩애플] 성능개선 1: 개발자 도구 & lazy import (0) | 2022.11.08 |
[코딩애플] react-query 사용하여 실시간 데이터 가져오기 (0) | 2022.11.08 |