📚 1. 사전 지식
React 렌더링 성능 최적화 방법
참고 : 렌더링 성능 최적화하는 7가지 방법, React.memo()로 최적화
1. useMemo : 함수에서 리턴되는 값을 메모이제이션
const average = useMemo(() => {
console.log("calculate average. It takes long time !!");
return users.reduce((acc, cur) => {
return acc + cur.score / users.length;
}, 0);
}, [users]);
2. React.memo : 컴포넌트 메모이제이션
순수 함수 컴포넌트와 렌더링 성능을 향상하는 기능. React.memo로 감싸진 함수의 결괏값은 메모리에 저장되는데, 같은 인풋값으로 해당 함수를 재호출 할 시, 저장해 둔 결과 값 반환.
React.memo를 사용하는 경우 : 컴포넌트가 같은 props로 자주 렌더링할 때 & 컴포넌트가 렌더링 될 때마다 복잡한 로직을 처리해야 할 때
3. useCallback : 함수 선언을 메모이제이션
4. 자식 컴포넌트의 props로 객체를 넘겨줄 경우 변형하지 말고 넘겨주기
5. 컴포넌트를 매핑할 때는 key값을 index를 사용하지 않는다
index 사용해도 무방한 경우 : 배열과 각 요소가 수정, 삭제, 추가 등의 기능이 없는 단순 렌더링만 담당하는 경우 & id로 쓸만한 unique 값이 없을 경우 & 정렬 혹은 필터 요소가 없어야 함
6. useState의 함수형 업데이트
const onRemove = useCallback(id => {
setTodos(todos => todos.filter(todo => todo.id !== id));
}, []);
7. input에 onChange최적화
onKeyUp={() => {
let searchQuery = searchRef.current.value.toLowerCase();
setTimeout(() => {
if (searchQuery === searchRef.current.value.toLowerCase()) {
setText(searchQuery);
}
}, 400);
}}
성능 측정 방법
Lighthouse
: Google Chrome에서 제공하는, 웹앱의 품질 개선에 도움을 주는 자동화 도구 입니다. 성능, 접근성, SEO 등 사이트에 대한 전반적인 진단을 할 수 있음
LCP(Largest Contentful Pain)
: 페이지의 메인 콘텐츠가 로드되었을 가능성이 있을 때 페이지 로드 로드 타임라인에 해당 시점을 표시하므로 사용자가 감지하는 로드 속도를 측정할 수 있는 중요한 사용자 중심 메트릭
- 페이지가 처음으로 로드를 시작한 시점을 기준으로 뷰포트 내에 있는 가장 큰 이미지 또는 텍스트 블록의 렌더링 시간을 보고
- LCP가 빠르면 사용자가 해당 페이지를 사용할 수 있다고 인지하는 데 도움이 됨
- 좋은 LCP 점수 : 2.5초 이하
- 고려하는 요소 : <img>, <svg> 내부의 <image>, <Video>, url() 함수를 통해 로드된 배경이미지 있는 요소
+ FCP(First Contetentful Pain) : 새로운 사용자 중심 성능 메트릭은 로딩 경험의 시작 부분만을 포착
🤩 2. 실제로 구현하기
1. home화면 -> 성능 측정 Error
lighthouse
LCP측정이 Error가 떴다. 그래서 chatGPT에게 물어보니
전부 내 사이트의 성능을 개선시키라는 내용이다.
1. 서버로부터 받는 시간을 체크하고 2. 이미지와 비디오를 최적화하고 3. 렌더링 하는 블록을 최소화하는 등 최적화를 더하라고 알려주었다.
일단, Lighthouse를 개선시키는 것이 목표였기에, home화면보다 기능이 더 적은 프로필 페이지를 개선시키기로 했다.
2. profile화면 -> LCP가 무려 17.8초?
성능이 47점에, LCP가 무려 17.8초가 걸렸다. 2.5초 이하가 좋은 LCP 점수라고 하였는데, 17.8 초라니..! LCP(Largest Contentful Pain)란 가장 큰 이미지 또는 텍스트 블록의 렌더링 시간이다. 어떻게 이 수치를 낮출 수 있을까?
3. React.memo로 컴포넌트 최적화하기
React.memo를 사용을 권장하는 경우는 1. 컴포넌트가 같은 props로 자주 렌더링할 때 2. 컴포넌트가 렌더링 될 때마다 복잡한 로직을 처리해야 할 때이다.
위 파란색 네모칸이 profile페이지의 Navigate컴포넌트이다.
// pages/profile/index.tsx
export default function Profile() {
...
return (
<Layout>
...
<Navigate
message="프로필"
isLeftButton={false}
handleLeftButton={() => {
router.push('/home');
}}
right_message={<NoticeIcon isSearch={false} />}
handleRightButton={() => {
router.push('/notice');
}}
/>
...
</Layout>
)
}
// components/common/Navigate.tsx
export default function Navigate({
...props
}){
return(
...
)
}
Navigate의 props로 message, isLeftButton, handleLeftButton(함수), right_message(노드), handleRightButton(함수)를 전달하고 있다. 같은 props를 계속 넘겨주기에 React.memo를 사용하기에 아주 적합한 컴포넌트라 생각하였다. 그래서 React.memo로 Navigate컴포넌트를 감싸고 다시 lighthouse를 측정해 보았다.
// components/common/Navigate.tsx
export default function React.memo(Navigate({
...props
}){
return(
...
)
})
!!!
LCP가 무려 3초나 감소하고, 성능 점수는 13점이 올랐다. 이 Naviagte컴포넌트 하나가 상당히 렌더링 하는데 시간을 소모하고 있었던 것이다.
다른 컴포넌트를 또 둘러보았다.
// pages/profile/index.tsx
<section className="flex justify-between gap-2 ">
{ActivityLists.map((activity: ActivityList) => (
<Activity
key={activity.id}
text={activity.text}
icon={activity.icon}
path={activity.path}
/>
))}
</section>
...
<section>
{SettingLists.map((setting: SettingList) => (
<SettingItem
key={setting.id}
text={setting.text}
path={setting.path}
/>
))}
</section>
Activity 컴포넌트와 SettingItem 컴포넌트 역시 같은 props를 계속 넘겨주고 있었다. 그래서 이 두 컴포넌트 또한 React.memo로 감싸주었다.
React.memo추가 후 다시 측정 후 LCP가 2초가 더 감소하여 12.9초가 되었다. 다른 컴포넌트를 둘러보았으나 props를 넘기지 않는 컴포넌트, 컴포넌트는 React.memo가 불필요하다고 판단하였다.
React.memo가 직접적으로 LCP를 줄이지는 않지만, 렌더링 최적화로 인해 Javascript 실행 시간이 줄어들어 브라우저가 LCP 관련 요소를 더 빨리 렌더링 할 수 있게 되었다.
🤔 2. 느낀 점 / 배운 점 / 추가로 공부할 것
전에 강의를 통해 React.memo에 대해 이론으로만 배운 적이 있다. 그 때 당시에는 전혀 와닿지 않았는데, 이렇게 실제로 내가 진행한 프로젝트의 LCP를 단축시키니 React.memo의 필요성과 존재의 의미에 대해 생각해 볼 수 있었다.
'개발 일지 > 개발 일지' 카테고리의 다른 글
[개발 일지] Next.js 블로그 분해기 (feat. Next.js 13) (0) | 2023.05.30 |
---|---|
[개발 일지] dependencies vs devDependencies 구분하기 (0) | 2023.05.18 |
[개발 일지] React Native 도전기 (1) (0) | 2023.05.16 |
[개발 일지] React성능 최적화 (2) : React Query의 캐싱 기능 (1) | 2023.05.05 |
[개발 일지] Next.js에서 접근성 향상하기 (접근성 점수 84점 -> 97점 향상시킨 썰) (0) | 2023.04.27 |
[개발 일지] React Query - Custom Hook 만들기 (+Typescript) (0) | 2023.04.13 |
[개발 일지] JWT + 소셜 로그인을 정복해보자 (React + Spring Boot, Kakao Login, Naver Login) (0) | 2023.04.11 |
[개발 일지] Intersection Observer API & react-query를 이용한 무한스크롤 구현 (0) | 2023.02.28 |