[React] 성능개선을 위한 팁 3가지

qwe8851·2022년 10월 18일
0

💎 React

목록 보기
26/37

성능개선을 위한 3가지 방법에 대해 소개하려고 한다.



✨ 1. 개발자도구 & lazy import


1-1. 확장프로그램 설치

개발 중 버그가 생길때 개발자도구에서 elements를 봐도 되지만 리액트개발자도구를 확장프로그램을 설치해서 보면 더 편함

React Developer Tools

React Developer Tools를 설치하고 개발자도구를 열어보면 Components탭과 Profiler탭이 생김

  • ⚛️Components

    여기서 개발중인 리엑트사이트를 컴포넌트로 미리볼 수 있음

    왼쪽에서 컴포넌트의 구조를 파악할 수 있고,
    왼쪽 상단 마우스이모티콘을 눌러 컴포넌트를 찍어보면
    거기 있는 state, props등을 조회&수정이 가능

    그리고 우측 상단 여러 버튼들로 컴포넌트가 사용되고 있는 코드를 확인하거나 디버깅 등이 가능함.

  • ⚛️Profiler


    Profiler탭에서 녹화버튼 클릭 후 페이지 이동이나 버튼조작을 하면
    방금 렌더링 된 모든 컴포넌트의 렌더링 시간을 측정해줌

    이상하게 느린 컴포넌트가 있다면 여기서 범인을 찾을 수 있음.


Redux Developer Tools

  • Redux store에 있던 모든 state를 전부 확인가능
    그리고 dispatch를 날릴 때 마다 뭐가 어떻게 바뀌었는지 로그를 남겨줌 (store가 복잡해질 때 유용)



1-2. lazy import

리액트는 build 시 html, js파일을 하나로 합쳐줌.(SPA)
그 안에 지금까지만든 모든 내용이 들어가있기에 파일사이즈가 좀 큼.

그래서 리액트사이트들은 첫 페이지 로딩속도가 매우 느릴 수 있다.

그게 싫다면 lazy import를 사용해 js파일을 잘게 쪼개면 됨.

예를 들어 페이지에서 Detail, Cart컴포넌트를 import해서 쓰고있다고 가정해보자.
하지만 메인페이지에서 Detail컴포넌트나 Cart컴포넌트들은전혀 보이고 있지 않다.

그럼 컴포넌트들이 필요해지면 나중에 import하면 될 듯?

사용법은 아래와 같다.

(기존사용법)

import Detail from './routes/Detail.js'
import Cart from './routes/Cart.js'

(lazy import 사용)

import {lazy, Suspense} from 'react'

const Detail = lazy( () => import('./routes/Detail.js') )
const Cart = lazy( () => import('./routes/Cart.js') )

<Suspense fallback={ <div>로딩중임</div> }>
  <Detail shoes={shoes} />
</Suspense>

이렇게 해주면 Detail컴포넌트 내용을 다른 js파일로 쪼개주기 때문에 첫 페이지 로딩속도를 향상시킬 수 있음.

lazy를 사용하면 Detail이나 Cart컴포넌트 로드까지 지연시간이 발생할 수 있다.

이때
1. Suspense import
2. Detail 컴포넌트를 감싸주면
Detail컴포넌트가 로딩중일 때 대신 보여줄 html을 작성할 수 있다.

+) <Suspense>로 <Routes>부분을 전부 감싸도 됨.






✨ 2. 재렌더링을 막는 memo, useMemo


컴포넌트가 재렌더링되면 그 안에 있는 자식컴포넌트도 항상 같이 재렌더링된다.

만약 자식컴포넌트의 렌더링시간이 오래걸리게 되면 부모컴포넌트를 누를 때마다 버벅거림이 발생할 수 있음.

이럴 때 자식컴포넌트를 memo로 감싸놓으면 됨.


function Child(){
  console.log('재랜더링됨');
  return <div>자식컴포넌트임</div>
}

function Parent(){
  let [count, setCount] = useState(0);
  
  return (
    <Child />
    <button onClick={()=>{ setCount(count+1) }}>+</button>
  )
}

Parent컴포넌트안에 Child컴포넌트를 만들었음

버튼을 누를 때마다 부모컴포넌트가 재렌더링되게 만들었는데
이 경우 Child컴포넌트도 재랜더링 됨

이때 memo함수를 스면 꼭 필요할 때만 <Child>컴포넌트를 재렌더링 해달라고 코드짤 수 있음


2-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>
  )
}
  1. memo import
  2. 원하는 컴포넌트 정의부분 감싸기
    이때 컴포넌트를 ler 컴포넌트명 = function(){}이런식으로 만들어야 감쌀 수 있음

이제 Child로 전송되는 props가 변하거나 할 때만 재렌더링 됨

❗ 주의
memo로 감싼 컴포넌트는 기존props와 바뀐props를 비교하는 연산이 추가로 진행되기 때문에 꼭 필요한 곳에만 사용할 것



2-2. useMemo

useMemo는 useEffet와 비슷함

컴포넌트 로드 시 1회만 실행하고 싶은 코드가 있으면 여기에 담으면됨.

import {useMemo, useState} from 'react'

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

function Cart(){ 

  let result = useMemo(()=>{ return 함수() }, [])

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

예를들어 반복문 100만번 돌려야하는 경우
그 함수를 useMemo안에 넣어두면 컴포넌트 로드 시 1회만 실행됨. (재랜더링마다 동작하지 않으니 좀 더 효율적)

useEffet처럼 dependency도 넣을 수 있어서 특정 stats, props가 변할 때 마다만 실행할 수도 있음.






✨ 3. useTransition, useDeferredValue


React 18버전 이후로부터 추가된 기능이 있다.

✔️ React v18 추가기능 1 : batching

automatic batching이라는 기능이 있는데

setCount(1)   // 재랜더링되지 않음
setName(2)    // 재랜더링되지 않음
setValue(3)   // 여기서 1번만 재렌더링 됨

state변경함수를 연달아 3개 사용하면 재렌더링도 원래 3번 되지만,
리액트에서 이를 마지막 1회에만 재랜더링해줌.

쓸데없는 재렌더링 방지기능이고 batching이라고 함.

fetch().then(()=>{
  setCount(1)     //재랜더링됨 
  setName(2)      //재랜더링됨
})

React v17까진 ajax요청, setTimeout안에 state변경함수가 있는 경우 일관적으로 batching이 일어나지 않았는데,
React v18부터는 어디에 있든 재랜더링은 마지막 1번만 됨

📎 참고
batching되는게 싫고 state변경함수 실행마다 재렌더링 시키고 싶으면 flushSync라는 함수를 쓰면 됨.


✔️ React v18 추가기능 2 : useTransition

렌더링 성능이 저하되는 컴포넌트에서 쓸 수 있는 혁신적인 기능이 하나 추가됨.

useTransition인데,
오래걸리는 부분을 이걸로 감싸면 렌더링 시 버벅임이 줄어듦.

여기를 참고해보면 이런 비유를 한다.

"주문을 할 때 하나 고를 때마다 직원이 주방으로 달려가지 않고, 오더를 완성시킬 때까지 대기하는 것과 같다."



3-1. useTransition

렌더링 시간이 매우 오래 걸리는 컴포넌트가 있다고 가정해보자.

버튼 클릭 타이핑 시 마다 그 컴포넌트를 렌더링해야 한다면 반응속도가 매우 늦으질텐데 이럴때 useTransition을 사용하면 됨.

재렌더링이 오래 걸리는 컴포넌트 만들어보기

import { useState } from 'react';

let a = new Array(10000).file(0);

function App(){
  let [name, setName] = useState('');
  
  return(
    <div>
      <input onChange={ (e)=>{ setName(e.target.value) }}/>
        {
		   a.map(()=>{
             return <div>{name}</div>
           })
        }
    </div>
  )
}
  • 테이터가 10,000개 들어있는 array자료를 하나 만들고
  • 그 갯수만큼 <div>를 생성
  • 그리고 유저가 타이핑할 수 있는 <input>도 만들었음

유저가 <input>에 타이핑하면 그 글자를 <div> 1만개 안에 집어넣어주어야 하는데
<div> 1만개를 렌더링해주느라 도 많은 지연이 발생한다.

타이핑한 결과가 n초씩 느리니 답답함..


startTransition

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은 startTransition()으로 감싼 코드가 처리중일때 true로 변하는 변수임

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

그래서 이런식으로 코드짜는것도 가능함

useDeferredValue위 코드는 useTransition으로 감싼게 아직 처리중이면 "로딩중"을 보여주고, 처리완료되면 <div>{name}</div>를 보여줌



3-2. useDeferredValue

startTransition() 와 용도는 같지만
useDeferredValue는 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>
  )
}

이렇게 쓰면 아까랑 같은 기능을 개발가능함.

useDefeferredValue안에 state를 집어넣으면 그 state가 변동사항이 생겼을 때 나중에 처리해줌.

그리고 처리결과를 let state에 저장해준다.

profile
FrontEnd Developer with Vue.js, TypeScript

0개의 댓글