본문 바로가기

FrontEnd

리액트 성능 최적화 : React.useMemo

반응형

TLDR :  CPU 자원을 많이 소비하는 연산에 useMemo를 사용한다.

사용자에게 최적의 경험을 선사하려면 60fps이상을 제공해야 한다.

(1000ms/60)  = 약 16.7ms 이니, 하나의 연산이 이 시간 이상 걸리면 안된다는 것을 의미한다.

npm run build / npm run serve를 통해 해당 최적화 조건을 확인한다.

 

 

크롬 개발자 도구의 성능 탭 > CPU를 6배 느리게 (본인은 맥os + edge를 활용함을 유의)

해당 화면을 프로파일링 해본다.

match-sorter라는 외부 라이브러리가 엄청난 cpu 연산을 사용하고 있다.

위 화면의 문제점은 리렌더링이 발생할 때마다 300ms정도 걸리는 연산을 계속 수행한다는 것이다.

다른 상태의 변화에 의해 해당 컴포넌트가 다시 호출될 때마다 300ms가 걸린다면 끔찍하다.

자세히 살펴보면 match-sorter의 연산이 많은 시간을 잡아먹고 있다.

해당 연산은 json을 불러와, 필터에 맞게 정렬하는 연산인데, 이미 정렬된 결과를 다시 정렬할 필요가 있을까?

이때 useMemo를 사용한다.

  // 🐨 wrap getItems in a call to `React.useMemo`
  const allItems = React.useMemo(() => getItems(inputValue), [inputValue])
  const items = allItems.slice(0, 100)

inputValue를 바꾸지 않고 forceRender를 여러번 수행해 봤다.

초기 계산 이후는 10ms로 매우 적은 포스 렌더링 시간이 걸린다.

webWorker로 성능 조금 더 개선하기

강사는 production에서 30ms 넘게 걸리는데, 내 pc는 개발 환경 15ms

여기서 웹워커를 사용하여 정렬 연산과 렌더링을 분리해 병렬 실행해보자.

useEffect를 통해 비동기로 cpu 연산을 패러랠하게 처리한다 (브라우저의 스레드 사용.)

workerize, workerize-loader 패키지를 사용한다.

// before : filter-cities.js
import {matchSorter} from 'match-sorter'
import cities from './us-cities.json'

const allItems = cities.map((city, index) => ({
  ...city,
  id: String(index),
}))

// for some reason workerize doesn't like export {getItems}
// but it's fine with inline exports like this so that's what we're doing.
export function getItems(filter) {
  if (!filter) {
    return allItems
  }
  return matchSorter(allItems, filter, {
    keys: ['name'],
  })
}
// after : 해당 파일을 webpack을 이용해 workerlize한다 한다.
// workerized-filter-cities.js

import makeFilterCitiesWorker from 'workerize!./filter-cities'

const {getItems} = makeFilterCitiesWorker()

export {getItems}

// 사용처
import {getItems} from '../workerized-filter-cities'
import {useAsync, useForceRerender} from '../utils'

// App 컴포넌트 내부....
  const {data: allItems, run} = useAsync({data: [], status: 'pending'})
  React.useEffect(() => {
    run(getItems(inputValue))
  }, [inputValue, run])
 // ...

15ms에서 약 11ms로 성능 개선, dep 배열 때문에 memo와 비슷한 효과
패러렐 처리는 invokeRegardedCallback을 통해 처리되는것으로 보인다.

해당 작업은 렌더링과 연산을 분리하여 패러랠하게 처리하므로, 사용자의 경험을 개선할 수 있다.

아래의 표를 참고한다.

 

CodeSandbox는 웹팩 설정을 건드리는 것이 불가능한 것으로 보인다.

웹팩 관련 코드는 로컬에서 테스트해보자.

참고 : 

react.js 내부 알아보기

Bogdan-Lyashenko/Under-the-hood-ReactJS: Entire React code base explanation by visual block schemes (Stack version) (github.com)

 

GitHub - Bogdan-Lyashenko/Under-the-hood-ReactJS: Entire React code base explanation by visual block schemes (Stack version)

Entire React code base explanation by visual block schemes (Stack version) - GitHub - Bogdan-Lyashenko/Under-the-hood-ReactJS: Entire React code base explanation by visual block schemes (Stack ver...

github.com

https://kentcdodds.com/blog/speed-up-your-app-with-web-workers

 

Speed up your App with Web Workers

How to get started using web workers for practical use cases

kentcdodds.com

https://github.com/kentcdodds/react-performance.git

 

GitHub - kentcdodds/react-performance: Let's make our apps fast ⚡

Let's make our apps fast ⚡. Contribute to kentcdodds/react-performance development by creating an account on GitHub.

github.com

 

반응형