프로젝트의 크기가 작으면 Rendering이 어떻게 되는지 신경을 쓰지않아도 작동이 잘 되지만, 데이터가 쌓이고 프로젝트 크기가 커지기 시작하면 다음과 같은 문제가 발생한다.
첫째, Re-Rendering이 언제일어나는지 웹의 동작을 잘 모름
둘째, Re-Rendering으로 함수호출이 계속일어나서 성능저하 이슈가 발생
Render단계 - React 요소 생성 React.createElement(더 알아보기)
컴포넌트 함수들이 호출된다.
컴포넌트 함수 호출 ➡️ 안에있는 자바스크립트 로직들이 수행 ➡️ 이를 기반으로 JSX로 작성된 UI가 리턴
Reconciliation(조정)단계 - 이전 element(요소)를 새 element(요소)와 비교(더 알아보기)
Commit(커밋)단계 - DOM을 업데이트
⭐️구성 element가 (state change되어) re-render된다고 해서 DOM 업데이트가 발생하는 것은 아니다.
->React는 state변화가 여러번일어났더라도 DOM업데이트는 여러번 일어나지않고 한번에 처리하며, recomciliation 단계에서 DOM update가 필요한지 판단해 변화가 있을 때만 업데이트한다.
Rendering은 html 요소(element), React 요소 등의 코드가 UI적요소로 볼 수 있도록 화면에 그려지는 것이다.
Re-Rendering은 Rendering이 다시 일어나는것
< Re-Rendering이 되는 경우 >
1. Props가 변경
2. State가 변경
3. 부모 컴포넌트가 re-render
4. Context value가 변경되었을 때
4가지 경우에 해당하면 re-render가 일어난다.
하지만! state와 prop이 여러개 존재한다면 하나의 state나prop이 변경되어도 re-render가 계속 일어날것이다.
또, 부모 State가 변경되어 부모컴포넌트가 re-render되고 자식 컴포넌트가 re-render되는 연쇄적인 방식으로 일어난다.
이러한 불필요한 re-render는 컴퓨터에 정말 안좋은경우이고 성능저하문제를 유발하기에 최적화 라는 작업이 필요하다.
최적화는 쉽게말하자면 re-render되는 횟수를 줄인다는 말이다.
따라서 re-render를 줄이려면 Props, State, 부모 컴포넌트의 re-rende횟수를 줄이거나 Context value의 변경을 줄이면 된다.
대부분의 최적화는 Props의 변경을 줄이는 방법을 쓴다.
하지만 가장 먼저 고려해야하는 것이 부모의 컴포넌트의 re-render을 줄이는 것이다.
import { useEffect, useRef, useState } from "react";
import "./App.css";
import DiaryEditor from "./DiaryEditor";
import DiaryList from "./DiaryList";
const App = () => {
👍🏻const [data, setData] = useState([]);
const dataId = useRef(0);
✌🏻const getData = async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/comments"
).then((res) => res.json());
const initData = res.slice(0, 20).map((it) => {
return {
author: it.email,
content: it.body,
emotion: Math.floor(Math.random() * 5) + 1,
created_date: new Date().getTime(),
id: dataId.current++,
};
});
✌🏻setData(initData);
};
useEffect(() => {
getData();
}, []);
const onCreate = (author, content, emotion) => {
const created_date = new Date().getTime();
const newItem = {
author,
content,
emotion,
created_date,
id: dataId.current,
};
dataId.current += 1;
setData([newItem, ...data]);
};
const onDelete = (targetId) => {
const newDiaryList = data.filter((it) => it.id !== targetId);
setData(newDiaryList);
};
const onEdit = (targetId, newContent) => {
setData(
data.map((it) =>
it.id === targetId ? { ...it, content: newContent } : it
)
);
};
👍🏻✌🏻const getDiaryAnalysis = () => {
console.log("일기분석시작");
const goodCount = data.filter((it) => it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100;
return { goodCount, badCount, goodRatio };
};
👍🏻✌🏻const { goodCount, badCount, goodRatio } = getDiaryAnalysis();
return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<div>전체 일기 : {data.length} 개</div>
<div>기분 좋은 일기 : {goodCount} 개</div>
<div>기분 안좋은 일기 : {badCount} 개</div>
<div>기분 좋은일기 비율 : {goodRatio} %</div>
<DiaryList onEdit={onEdit} onDelete={onDelete} diaryList={data} />
</div>
);
};
export default App;
1)const getDiaryAnalysis = () => {
console.log("일기분석시작");
const goodCount = data.filter((it) => it.emotion >= 3).length;
const badCount = data.length - goodCount;
const goodRatio = (goodCount / data.length) * 100;
return { goodCount, badCount, goodRatio };
};
=getDiaryAnalysis함수가 실행될때마다 일기분석시작이 콘솔에 뜨게됨
goodCount는 filter를 사용하여 감정점수가 3이상인일기들의 개수만 count한다
badCount는 전체개수-goodCount
goodRatio는 goodCount를 전체개수로 나눈 후 100을 곱해줌
이 세가지를 객체로 담아 return한다.
2)const { goodCount, badCount, goodRatio } = getDiaryAnalysis();
=getDiaryAnalysis를 지역함수로 만든것이므로 return전에 호출을 해준다
함수를 호출한 결과값을 객체로 반환하게되니까 똑같이 객체로 비구조화할당으로 받는다
3)return (
<div className="App">
<DiaryEditor onCreate={onCreate} />
<div>전체 일기 : {data.length} 개</div>
<div>기분 좋은 일기 : {goodCount} 개</div>
<div>기분 안좋은 일기 : {badCount} 개</div>
<div>기분 좋은일기 비율 : {goodRatio} %</div>
<DiaryList onEdit={onEdit} onDelete={onDelete} diaryList={data} />
</div>
);
};
= 코드의 순서대로 화면에 표시해준다.
다 입력하고 페이지에가서 콘솔창을 확인해보면 일기분석시작이 두번 실행(re-rendering)된것을 확인할 수 있다.
👍🏻App.js컴포넌트가 처음 시작(Mount)될때 data의 useState의 값은 빈배열 이었는데 그 순간에 getDiaryAnalysis값을 한번 호출하게되고 그때 일기들이 0개로 로딩됨
✌🏻그 다음 getData의 API호출이 성공하고 setData가 이루어지게되면서 data가 한번 바뀌게된다. App.js컴포넌트가 re-render가 일어나게되고 그 안에 있던 함수들이 재생성하게되고, const{goodcount,badcount,goodratio}
함수가 다시 실행이 되면서 getDiaryAnalysis가 다시 호출이 일어나게 된다. 그래서 총 2번 콘솔에 찍힌것!!!
함수형컴포넌트를 만들었다는것은 JS의 함수를 쓰고 함수형 컴포넌트는 단지 JSX를 반환하는 함수이다.
App함수가 포함하고있는 JSX문법의 html요소들은(즉,DOM요소들) 화면에 반영이 될 뿐, JS의 함수가 호출되고 반환되는것은 똑같이 일어난다.
Re-Rendering이 된다는것은 App함수가 한번 더 실행이된다는 말(=자바스크립트의 함수가 호출)
App.js컴포넌트가 re-render가일어난다면 당연히 getDiaryAnalysis함수도 재실행이된다
갯수에 영향을 주지않는 내용을 수정해도 getDiaryAnalysis가 re-rendering이 일어난다
return을 가진 함수를 memoization 해서 연산들(=함수들)을 최적화하기 위해서는 useMemo함수를 사용한 예제를 다음 useMemo()포스팅에 넣어볼 예정이다.