하루하루 꾸준히, 인생은 되는대로

리액트

언제 컴포넌트가 랜더링 되는 걸까?

긤효중 2023. 4. 1. 23:16

랜더링이란?

리액트는 컴포넌트를 랜더링한다.

컴포넌트가 스스로의 상태를 능동적으로 바꾸든, 다른 변화 떄문에 수동적으로 바뀌었든,

컴포넌트는 리액트에 의해 호출된다.

 

컴포넌트가 상태 업데이트를 했다고 해서, 반드시 하나의 랜더링으로 이어지는 것은 아니다

 

리액트는 컴포넌트의 상태의 변화가 의미 없는 변화로 해석할 지도 모른다!

리액트에서 상태 업데이트를 모아서 한번에 처리하고(batch update) 이 일괄 업데이트로,

불필요한 랜더링 횟수를 최소화 한다.

 

리액트는 다양한 이유로 컴포넌트를 랜더링 할 수 있기 때문에 컴포넌트를 랜더링 하는 것이

즉각적인 UI 변화로 이어지지 않을 수 있다


랜더링 과정 살펴보기

리액트는 컴포넌트의 루트부터 시작하면서 (아래의 코드에는 root부터 ),

아래쪽으로 쭉 훑어보면서, 업데이트가 필요하다고 플래그가 설정되있는 모든 컴포넌트를 찾는다.

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

플래그가 설정되어있는 컴포넌트를 만난다면 classComponentInstance.render()를 호출하고,

함수형 컴포넌트 경우에는 FunctionComponent()를 호출하고 랜더링 결과를 저장한다

 

컴포넌트의 렌더링 결과물은 JSX문법으로 구성되어 있으며 JS가 컴파일되고,

배포 준비가 끝난다면 React.createElement()를 호출하고 결과를 저장한다.

 

예를 들어서 다음의 JSX구문이 있으면 결과는 이렇다.

//일반적으로 사용하는 JSX 문법
return <MyComponent a = {10} b = "오늘날씨">맑음</MyComponent>

//react.createElement을 호출해 변환한다
return react.createElement(MyComponent,{a:10,b:'오늘날씨'},맑음)

//호출결과 element를 나타내는 객체로 바꾼다
{type:MyComponent,props:{a:10,b:'오늘날씨'},children:['맑음']}

 

리액트에서는 Reconciliation이라는 과정이 존재한다.

전체 컴포넌트에서 위와 같은 랜더링 결과를 수집해서,

기존 DOM과 새로운 오브젝트 트리(가상DOM)의차이를 비교하고 계산한다.

 

이 Reconciliation과정을 2가지로 구분하는데, Render PhaseCommit Phase로 구분한다

 

Render Phase : 컴포넌트를 랜더링하고, 변경사항을 계산하는 모든 작업

Commit Phase : 돔에 변경사항을 저장하는 과정

 

커밋 페이즈단계에서 useLayoutEffect 훅을 실행한다. 


useEffect hook과 useLayoutEffect hook!

 

useEffect

 

useEffect는 Render와 Paint과정을 모두 거친 후 실행된다.

 

Render는 DOM tree를 구성하기 위해 엘리먼트의 스타일 속성을 계산하는 과정이고Paint는 실제 스크린에 Layout을 표시하고 업데이트 하는 과정이다.

 

useEffect는 이 2가지 과정(Render,Paint)을 거치고 나면, 비동기적으로 실행된다.Paint후 실행되기 떄문에(실제 스크린에 Layout을 표시한 뒤에)useEffect 안에 DOM에 영향을 준다면 , 깜빡임을 볼 수 있게 된다

 

 

useLayoutEffect

 

useLayoutEffect는 Render 과정을 거친 후 , 실행되고  그 이후 Paint과정을 거친다이 과정은 동기적으로 진행된다Paint전에 실행되기 때문에 DOM에 영향을 주더라도 깜박임을 볼 수 없게 된다.

 

https://junghyeonsu.com/posts/useeffect-vs-uselayouteffect/

 

useEffect와 useLayoutEffect 차이점 (React) - 정현수 기술 블로그

useEffect와 useLayoutEffect 차이점에 대해 알고 계셨나요.

junghyeonsu.com


# 부모 컴포넌트가 바뀔때

부모가 완전히 다른 타입의 엘리먼트가 되면 그 아래는 전부 변했다고 여긴다

<div>
<Card />
</div>

//아래처럼 바뀌면 Card 컴포넌트는 완전 새 컴포넌트로 리랜더링 된다
<h1>
<Card />
</h1>

 

props와 state변화를 감지해 업데이트 하는 경우

 

props나 state에 따라 필요한 부분을 다시 랜더링한다.

부모가 리랜더링되는 경우 자식들도 모두 리렌더링된다

 

<Parent>
<Childern state = {true} />
</Parent>

//자식 컴포넌트가 밑의 경우에는 바뀐다
<Parent>
<Children state = {false} />
</Parent>

Props로 객체를 넘기지 말자

리액트는 이전 props, 이후 props가 다른 경우 다시 랜더링을 수행한다.

객체를 props로 전달할 때, 이전과 같은 props라도 다르다고 인식된다.

 

이를 자바스크립트가 객체를 비교하고, 인식하는 방법에 대해 알아보았다.

 

먼저 다음의 코드는 콘솔에서 다르다고 출력된다.

aaa와 bbb는 참조형 타입의 객체를 만들고, 두 객체는 주소가 다르다.

변수 aaa와 bbb는 각자 다른 주소를 바라보고 있으므로 두 객체는 다르다고 나온다.

 

밑의 코드도 마찬가지이다.

{}, {}라는 두 객체를 생성하고, 빈 객체는 각자 다른 주소를 바라보므로 이는 엄연히 다른 객체이다.

 

Object는 참조형이기 때문에 object를 ===로 비교할 수 없다.

Object는 참조형이기 때문에, 원시형으로 바뀌어서 비교해야 한다.

 

Object를 비교하는 방법은 여러 가지가 있는데, Object.entries,JSON.stringift를 사용하면, 잘 비교가능하다.


그래도 props를 객체 넘겨야 하는 경우는 어떻게 해야할까? 

 

컴포넌트나 props를 기억해두자->memo,useMemo Hook!

 

특정 값이나 props를 다시 연산하지 않고 사용하는 방법을 memorization이라고 하고, 컴포넌트, props를

memorization해두면, 리액트는 해당 컴포넌트가 바뀌지 않았다고 판단한다.

 

하나하나씩 살펴보자.먼저 useMemo훅에 대해 알아보자.


useMemo

useMemo가 진짜 필요한지, 사용해야만 하는지는 반드시 살펴보아야 할 것 같다.

나도 막연하게 useMemo가 memorization을 통해 최적화를 해준다고 어렴풋이 알고 있었다.

그러나 성능에 도움이 되지 않는 useMemo는 필요 없다고 생각한다.

 

먼저 useMemo훅에 대해 간단히 살펴보면,

useMemo란 성능 최적화를 위하여 연산된 값을 재사용하는 기능을 가진 함수이다.

첫번쨰 파라미터에는 어떤 연산을 할 건지 넣고, 두번쨰는 useEffect와 마찬가지로, 의존성 배열을 넣어준다.

이 배열안의 값이 변경되면 함수를 호출해 값을 연산한다.

import React, { useMemo, useState } from 'react';

function MyComponent(props) {
  const [count, setCount] = useState(0);

  // 연산이 복잡한 경우, useMemo()를 사용하여 최적화할 수 있습니다.
  const result = useMemo(() => {
    let sum = 0;
    for (let i = 1; i <= count; i++) {
      sum += i;
    }
    return sum;
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

위에서 result변수는 count가 바뀔때마다 바뀌게 된다. useMemo를 사용해 count가 바뀌지 않으면,

result는 이전에 계산된 count가 재사용된다.이렇게 memorization을 통해 불필요한 계산을 막을 수 있다.

 

밑의 주소는 useMemo가 어느 순간부터 성능상 도움이 되는지 정리해 놓은 글이다.

 

데이터의 양과 데이터를 어떻게 처리하는지에 따라 달라질 수 있지만,  데이터의 크기가 크지 않을떄,

오히려 useMemo 훅을 사용하는 것은 과연 바람직할까?에 대한 의문이 들었다.

성능상 이점을 가져가지 위해서는 밑의 글처럼 실제 도움이 되는지 측정하면서 개발하는 것도 좋은 방식인 것 같다!.

 

https://github.com/yeonjuan/dev-blog/blob/master/JavaScript/should-you-really-use-usememo.md?utm_source=substack&utm_medium=email 

 

GitHub - yeonjuan/dev-blog: 개발 블로그, 공부한거 정리

개발 블로그, 공부한거 정리. Contribute to yeonjuan/dev-blog development by creating an account on GitHub.

github.com