[React] Rendering 최적화 (feat.일기장)

Hyun·2022년 1월 7일
0

React

목록 보기
14/22
post-thumbnail

💡'Rendering 최적화' 란?

프로젝트의 크기가 작으면 Rendering이 어떻게 되는지 신경을 쓰지않아도 작동이 잘 되지만, 데이터가 쌓이고 프로젝트 크기가 커지기 시작하면 다음과 같은 문제가 발생한다.
첫째, Re-Rendering이 언제일어나는지 웹의 동작을 잘 모름
둘째, Re-Rendering으로 함수호출이 계속일어나서 성능저하 이슈가 발생

DOM업데이트 순서

  1. Render단계 - React 요소 생성 React.createElement(더 알아보기)
    컴포넌트 함수들이 호출된다.
    컴포넌트 함수 호출 ➡️ 안에있는 자바스크립트 로직들이 수행 ➡️ 이를 기반으로 JSX로 작성된 UI가 리턴

  2. Reconciliation(조정)단계 - 이전 element(요소)를 새 element(요소)와 비교(더 알아보기)

  3. Commit(커밋)단계 - DOM을 업데이트

⭐️구성 element가 (state change되어) re-render된다고 해서 DOM 업데이트가 발생하는 것은 아니다.
->React는 state변화가 여러번일어났더라도 DOM업데이트는 여러번 일어나지않고 한번에 처리하며, recomciliation 단계에서 DOM update가 필요한지 판단해 변화가 있을 때만 업데이트한다.

Re-Rendering?

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의 변경을 줄이는 방법을 쓴다.

  • 불변객체(immutable.js 등)을 사용
  • useMemo, useCallback을 사용

하지만 가장 먼저 고려해야하는 것이 부모의 컴포넌트의 re-render을 줄이는 것이다.


📖예제

App.js코드

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이 일어났다

다 입력하고 페이지에가서 콘솔창을 확인해보면 일기분석시작이 두번 실행(re-rendering)된것을 확인할 수 있다.

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()포스팅에 넣어볼 예정이다.


🚀참고자료

DOM업데이트
React강의-이정환강사

profile
FrontEnd Developer (with 구글신)

0개의 댓글