성능개선1: 개발자도구 & lazy import
props 를 보냈는데 출력이 안된다거나 이미지를 넣었는데 안보이는 버그같은게 생기면 개발자도구를 켜서 Elements 탭을 살펴보면된다.
이 탭에서는 내가 짠 코드가 실제 html css 로 변환되어 보여지는데, 컴포넌트로 보고 싶다면 리액트 개발자도구를 설치해서 보면된다.
- 크롬 확장 프로그램: React Developer Tools
Chrome Web Store
브라우저에 새로운 기능을 추가하고 탐색 환경을 맞춤설정합니다.
chromewebstore.google.com
크롬 웹스토어에 들어가서 React Developer Tools 을 검색하고 설치한 뒤에 개발자도구에 들어가면 Components 탭이 생기는데, 여기서 개발중인 리액트사이트를 컴포넌트로 미리 볼 수 있다.
왼쪽에서 컴포넌트 구조 파악이 가능하고 왼쪽 상단 아이콘을 눌러 컴포넌트를 눌러보면 거기 있는 state, props 들 조회가 가능하고, 수정해볼 수도 있다.
- Profiler 탭에서 성능평가 기능
녹화버튼을 누르고 페이지 이동이나 버튼 조작을 해보고 녹화를 끝내면 방금 렌더링된 모든 컴포넌트의 렌더링 시간을 측정해준다.
이상하게 느린 컴포넌트가 있다면 이걸 이용해서 범인을 찾을 수 있다. 지연의 원인은 보통 서버에서 ajax 요청 결과가 늦게 도착해서인 경우가 많은데 서버가 느린 것은 어쩔 수 없다..
- Redux Developer Tools
이것도 크롬 웹스토어에서 설치 가능하다. Redux store 에 있던 state 전부 확인 가능하고 dispatch 를 날릴 때마다 어떻게 바뀌었는지 로그를 작성해주기 때문에 store 가 복잡해지면 유용하다!
- lazy import
코드를 다 짰다면 npm run build 를 해서 짰던 코드들을 html css js 파일로 변환해야 한다.
리액트로 만드는 Single Page Application 의 특징은 html, js 파일이 하나만 생성된다. 그 안에 지금까지 만든 모든 내용이 들어가 있기 때문에 파일의 사이즈가 좀 크다. 그래서 원래 리액트 사이트들은 첫 페이지 로딩속도가 매우 느리다.
속도를 개선하고 싶다면 lazy import 를 사용해서 js 파일을 쪼개면 된다.
메인페이지에서 Detail, Cart 를 import 해서 사용하고 있는데, Detail, Cart 컴포넌트는 지금 메인페이지에서 전혀 보이지 않는다고 가정해보자.
이때 lazy import 문법을 사용해서 Detail, Cart 컴포넌트가 필요해질 때 import 하도록 만들면 된다. 이렇게 하면 Detail 컴포넌트 내용을 다른 js 파일로 쪼개어주기 때문에 첫 페이지 로딩속도를 향상시킬 수 있다.
// App.js
import Detail from './routes/Detail.js'
import Cart from './routes/Cart.js'
lazy import 사용
// App.js
import {lazy, Suspense, useEffect, useState} from 'react'
const Detail = lazy( () => import('./routes/Detail.js') )
const Cart = lazy( () => import('./routes/Cart.js') )
근데 단점은 lazy import 를 사용하면 Detail, Cart 컴포넌트 로드까지 지연시간이 발생할 수 있다는 것이다.
그럴 땐 Suspense 를 사용하면 된다.
1. import { Suspense } from 'react'
2. lazy 사용으로 지연시간이 발생할 수 있는 컴포넌트 감싸기 (컴포넌트가 로딩중일 때 대신 보여줄 html 도 작성 가능)
(사실 귀찮으면 <Suspense> 로 <Routes> 를 통채로 감싸도 된다)
<Suspense fallback={ <div>로딩중임</div> }>
<Detail shoes={shoes} />
</Suspense>
성능개선2: 재렌더링 막는 memo, useMemo
컴포넌트가 재렌더링되면 거기 안에 있는 자식컴포넌트는 항상 함께 재렌더링 된다.
평소엔 별 문제 없겠지만 자식컴포넌트가 렌더링시간이 오래걸리는 무거운 컴포넌트라면 문제가 생긴다.
(부모컴포넌트에서 버튼을 누를 때마다 버벅이는 불상사가 발생할 수도 있다는 것..)
그럴 땐 자식을 memo 로 감싸놓으면 된다!
Cart 컴포넌트 안에 Child 컴포넌트를 만들었다. Cart 컴포넌트에서 버튼을 누를 때마다 Cart 컴포넌트가 재렌더링되도록 만들었는데, 이때 자식 컴포넌트인 Child 도 재렌더링된다.
function Child(){
console.log('재렌더링됨')
return <div>자식임</div>
}
function Cart(){
let [count, setCount] = useState(0)
return (
<Child />
<button onClick={()=>{ setCount(count+1) }}> + </button>
)
}
- 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>
)
}
1. memo() 를 사용하기 위해 'react' 라이브러리로부터 memo 를 import
2. 원하는 컴포넌트 정의부분을 감싸기
( 컴포넌트를 let 컴포넌트명 = function() { } 이런 식으로 만들어야 감쌀 수 있다. )
감싸면 Child 로 전송되는 props 가 변하는 경우에만 재렌더링 된다.
※ 참고
그렇다면 memo 는 좋은 것이니까 아무 곳에나 다 써도 될까? (X)
memo 로 감싼 컴포넌트는 헛된 재렌더링을 막기 위해 기존 props 와 바뀐 props 를 비교하는 연산을 추가로 수행하는데, props 가 크고 복잡하면 이것 또한 부담이 될 수 있기 때문에 꼭 필요한 곳에만 사용하는 것이 좋다!
- useMemo
memo 와 비슷한 useMemo 라는 것도 있다. useEffect 와 비슷한 용도로 사용한다. 컴포넌트 로드시 1회만 실행하고 싶은 코드가 있다면 여기에 담으면 된다.
import {useMemo, useState} from 'react'
function 함수(){
return 반복문10억번돌린결과
}
function Cart(){
let result = useMemo(()=>{ return 함수() }, [])
return (
<Child />
<button onClick={()=>{ setCount(count+1) }}> + </button>
)
}
1. 반복문을 10억번 돌려야 하는 경우
2. 그 함수를 useMemo 안에 넣어두면 컴포넌트 로드시 1 회만 실행됨
재렌더링이 될 때마다 10억번의 반복문을 돈다면 성능 저하가 일어날 것이다. 그렇기 때문에 재렌더링할 때마다 새롭게 반복문을 돌지 않도록 useEffect 를 사용하는 것이다. ( 재렌더링일 일어날 때마다 동작하는 것이 아니기 때문에 효율적 )
useMemo 는 useEffect 처럼 dependency 도 넣을 수 있어, 특정 state 나 props 가 변할 때만 코드가 실행되도록 할 수도 있다.
성능개선3: useTransition, useDeferredValue
- 리액트 18버전부터 추가된 기능 1: 일관된 batching
state변경함수1()
state변경함수2()
state변경함수3()
state변경함수를 연달아 3개를 사용하면 원래는 함수가 사용될 때마다 재렌더링이 일어나 총 3번의 재렌더링이 일어나야 하지만, automatic batching 기능으로 인해 마지막 state변경함수가 실행된 후에 1번만 재렌더링이 일어난다.
( 이렇게 성능저하를 일으키는 쓸데없는 재렌더링을 방지하는 기능을 batching 이라고 한다 )
리액트 17버전까지는 ajax 요청, setTimeout 안에 state변경함수가 있는 경우에는 batching 이 일어나지 않았는데, 리액트 18버전 이후부터는 state변경함수가 어디에 있든 재렌더링은 마지막에 1번만 일어나도록 바뀌었다.
리액트 18버전부터 추가된 기능2: useTransition
렌더링시간이 매우 오래걸리는 컴포넌트가 있다고 할 때,
버튼클릭, 타이핑할 때마다 그 컴포넌트를 렌더링해야한다면 버튼클릭, 타이핑 반응속도도 느려진다.
사람들은 원래 클릭, 타이핑을 했을 때 0.3초 이상 반응이 없으면 불편함을 느끼기 깨문에 개선해야한다.
당연히 그 컴포넌트 안의 html 갯수를 줄이면 대부분 해결되지만, 그런게 안된다면 useTransition 기능을 사용하면 된다.
재렌더링이 느린 컴포넌트 만들었음
- 데이터가 10000개 들어있는 array 자료를 만들고
- 유저가 <input> 태그에 타이핑한 문자를 array 자료의 갯수만큼 <div> 를 생성하게 만듦
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>
)
}
유저가 <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
startTransition() 으로 감싼 코드가 처리중일 때 true 로 변하는 변수이다. 아래와 같이 사용하면 된다.
useTransition 으로 감싼게 처리중일 때는 로딩중이라는 글씨가 보이고, 처리가 완료되면 <div> 태그가 보이게 된다.
{
isPending ? "로딩중" :
a.map(()=>{
return <div>{name}</div>
})
}
useDeferredValue
startTransition() 과 용도가 비슷한데, 다른 점은 state 아니면 변수 하나를 집어넣을 수 있게 되어있다.
그래서 집어넣은 state 나 변수에 변동사항이 생기면 그걸 늦게 처리해준다.
(이 코드는 useTransition 파트에 있던 코드와 동일한 기능을 한다.)
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>
)
}
useDeferredValue 안에 state 를 집어넣으면 그 state 가 변동사항이 생겼을 때 나중에 처리해주고, 처리결과는 let state 에 저장해준다.
※ 참고
페이지에 많은 html 을 집어넣을 경우 리액트가 다른 프레임워크나 쌩자바스크립트보다 속도가 느리다는 벤치마크 결과가 많다고 한다.
한 페이지에 html 태그가 1만개가 넘어 성능이 저하된다면, 그냥 html 태그들을 페이지 여러개로 나누어 놓는 것이 좋다.
'웹 > 24-StudyWithPnP' 카테고리의 다른 글
[React.js] 1주차 - Node.js, React.js (1) | 2024.10.07 |
---|---|
[React+JS/리액트] 웹 스터디 - state 변경함수 사용할 때 주의점: async (0) | 2024.08.06 |
[React+JS/리액트] 웹 스터디 10주차 (0) | 2024.07.11 |
[React+JS/리액트] 웹 스터디 9주차 (0) | 2024.07.04 |
[React+JS/리액트] 웹 스터디 8주차 (0) | 2024.06.12 |