Front_End [JS기반]/React.js + Next.js

[ 3D Web : Front_React.js ] React의 동작원리 정리.

안다미로 : Web3 & D.S 2024. 11. 21. 16:01

 

 

 

 

[ 3D Web : Front_React.js ]  React의  동작원리 정리.

 


 

 

∇ Front_React : React의 동작원리.

목  차

1. React란?
2. 돔(DOM)이란?
3. 가상돔(Virtual DOM)이란?
3. React의 생명주기.

 

 


Ⅰ. React란?


        ☆ React는 사용자 인터페이스를 구축하기 위한, 선언적이고 효율적이며 유연한 JS라이브러리입니다.

             "컴포넌트"라고 불리는 코드블록*파편을 이용하여 복잡한 UI를 구성하도록 돕습니다.

 

 

              ◇ React는 사용자 인터페이스를 구축하기 위한 JavaScript 라이브러리.

              ◇ UI를 구성하는 컴포넌트 기반의 개발을 지원합니다.

              ◇ React에서의  "가상 DOM "은 React가 UI업데이트를 효율적으로 처리하기 위해서 사용하는 개념입니다.

 

 

 

 


 

 

Ⅱ. 돔( Dom) 이란?


      - DOM( Document Obejct Model ) 은  "문서 객체 모델"을 의미합니다.

 

          ※ 문서 객체 모델(DOM)이란?? 

 

                - 웹 페이지를 구성하는 HTML의 (head, body) 같은 태그들을 JS가 이해할 수 있는 

                    (메모리에 보관할 수 잇는) 객체 모델을 의미합니다.

 

              - DOM은 "Document Object Model"의 약어로,  

                웹 페이지의 구조화된 내용을 표현하고 상호작용 할 수 있는 방법들을 제공하는 프로그래밍 인터페이스입니다.

 

 

          -  Dom은 웹 페이지의 요소들을 계층 구조로 나타내며,  각 요소는 객체로 표현됩니다.

                  ->> 이 객체들은 JS와 같은 스크립팅 언어를 사용하여,  

                             동적으로 화면을 조작하거나 상호작용할 수 있는 인터페이스를 제공 !!

                      ex) DOM을 사용하면 페이지의 특정 요소의 내용을 변경하거나, 스타일을 수정, 이벤트 처리 등 가능.

 

<!DOCTYPE html>
<html>
<head>
  <title>DOM Example</title>
</head>
<body>
  <h1>Hello, DOM!</h1>
  <p>DOM 예시 코드 입니다.</p>
</body>
</html>

위 코드를 DOM 구조로 표현해주면,

Document
├── html
│   ├── head
│   │   └── title
│   │       └── Text: "DOM Example"
│   └── body
│       ├── h1
│       │   └── Text: "Hello, DOM!"
│       └── p
│           └── Text: "DOM 예시 코드 입니다."

이런 식의 구조로 표현가능합니다.

 

 

JS를 사용하여, 위에서 말한 DOM에 접근하고 조작 가능합니다. 

const titleElement = document.querySelector('h1');
titleElement.textContent = 'DOM을 변경합니다';

const paragraphElement = document.querySelector('p');
paragraphElement.textContent = '여기도 DOM을 변경합니다';

                


 

 

Ⅲ. 가상-돔( Virtual - Dom) 이란?


§ Virtual DOM(VDOM) 은 UI의 이상적인 혹은 "가상"적인 표현을 메모리에 저장하고,
   React-DOM과 같은 라이브러리에 의해 "실제" DOM과 동기화하는 프로그래밍 개념입니다.

    

 

      - 가상 DOM 이란, 실제 DOM의 구조를 분석하여,  메모리에 저장하고 관리하는 객체.

      - 페이지 랜더링시마다, 새로운 가상DOM을 생성하여, 

           상태값 변경 이전-이후를 비교하여 달라진 부분을 반영하는 매커니즘을 사용합니다.

 

 

 

      √  가상 DOM은 실제 브라우저 DOM과 별개로,  React가 메모리 상에 유지하는 가상-DOM-트리 입니다.

 

                 - React의 가상-DOM은 실제-DOM과 동기화되어 있고,

                   React 컴포넌트의 상태나 프로퍼티의 내용이 변경될 때마다 가상-DOM을 업데이트합니다.

 

 

                - React는 "가상 DOM의 변화를 분석"하여,  "실제 DOM에 필요한 최소한의 변경만을 적용" 합니다.

                      ==>> 브라우저의 성능을 향상시키고,  업데이트 과정을 최적화하는데 도움이 됩니다.

 

 

    ◎ 가상돔은 무엇을 해결하기 위해서 등장했는가??

 

            ::   "DOM 렌더링 과정의 비효율적인 문제"를 해결하기 위해서.

 

               *DOM 렌더링의 비효율적인 문제*

                     - React는 SPA(Singel Page Application) 입니다.

                            ==  SPA이기 때문에,  DOM의 복잡도가 증가하게 되고, 렌더링 빈도가 잦아지게 됩니다.

                               === PC 자원을 더 소모하게 되는 문제.

 

 

               @브라우저의 기본 구조를 살펴보면,

브라우저의 기본 구조

 

   여기서 봐야 할 건,

 

- 사용자 인터페이스

      :  주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분.


- 브라우저 엔진

      : 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.
         (ex. 크롬 - 블링크, 웹키트...)


- 렌더링 엔진

       :  브라우저 엔진으로부터 전달받은 HTML, CSS를 파싱하여 요청한 콘텐츠를 표시.
         (ex. HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함 )

 

 

요약하자면,  브라우저 엔진은 서버에서 받은 자료를 기반으로 렌더링 엔진을 통해 요청받은 콘텐츠를 표시합니다.

 

위쪽에 코드에서 말했던 querySelector를 사용하거나하여서 페이지의 속성을 변경하면

DOM이 변경된 것을 인지하고 브라우저 엔진은 위와 같은 렌더링 과정을 반복하게 되는 것입니다.

 

 

** 이렇게 불필요하게 렌더링을 반복하는 부분을 VirtualDOM은 어떻게 해결했는가???

 

        == 가상돔이라는 DOM을 가상의 객체 메모리에 미리 만들어 놓는 것!

 

          ◆ 가상 돔의 실행순서.

 

                   1. " 상태 및 프로퍼티의 변경 감지 " 

                         : React 컴포넌트의 상태나 프로퍼티가 변경되면, React는 가상-DOM에 변경 사항을 기록합니다.

 

 

                   2. " 가상 DOM 업데이트 " 

                         : 변경된 부분만을 포함하는 가상 DOM트리가 생성됩니다.

                              -->> 이 트리는 메모리 내에서만 존재하며, 실제 DOM과는 별개입니다.

 

 

                   3. " 가상 DOM 비교 " 

                         : " 이전 상태의 가상 DOM과 새로운 가상 DOM을 비교하여, 변경된 부분을 식별" 합니다.

                              -->> 이 과정에서,  어떤 요소가 추과된건지,  변경된건지,  제거되었는지를 판단합니다.

 

 

                   4. " 실제 DOM 업데이트 " 

                         : " 변경된 부분만을 실제 DOM에 적용 " 합니다.

                              -->> 이 때,  최소한의 DOM조작을 수행하여 성능을 최적화합니다.

 

     ★ 가상-DOM과  실제-DOM을 비교하고, 변경된 부분만을 실제 DOM에 적용함으로서 불필요한 렌더링을 해결.

 

 


 

 

Ⅳ. React의 생명주기.


     - 위에서 말했듯이,  리액트는 "컴포넌트" 기반의 View를 중심으로 한 라이브러리입니다.

 

      - View를 구성하는 각각의 컴포넌트에는 라이프사이클, 즉 "컴포넌트 수명 주기"가 존재합니다.

 

     - " 컴포넌트의 수명 "은 보통 페이지에서 렌더링되기 전인 준비 과정에서 시작하여,

             페이지에서 사라질 때 끝이 납니다.

  

 

 

     - "라이프 사이클"을 다룬다는 것은,

        " 컴포넌트가 생격나고, 변화하고, 없어지는 일련의 프로세스를 개발자가 통제하는 것 " 을 의미합니다.

 

             [* 리엑트 개발을 할 때, 우리는 HTML을 작성하는 것이 아닌,

              JSX를 작성했기 때문에, 생명주기에 대해서 알고 있어야 합니다. ]

                == 리액에트에서 'render'메서드만으로 어떻게 HTML을 그려내는지 알 수 있게 됩니다.

 

 

     -  " 생성자를 통해서 필요한 메모리를 할당하고, 객체의 역할이 끝나면 소멸자를 통하여 메모리를 반환 " 합니다.

             - > 컴퓨팅 자원은 한정적이기 때문에 역할이 끝나면,

                  모든 메모리를 반환해야 메모리 누수를 방지하고 더 좋은 성능을 발휘 가능.

 

             ☆ "라이프 사이클이 있는 이유는 메모리 비우기가 가장 큰 이유 ! "


 

 

  ∇ 라이프 사이클의 분류.


          - "라이플 사이클"은 총 9가지로 분류됩니다.

 

클래스 방식 라이프 사이클

 

9가지를 다시,  3가지 대분류 유형으로 구분 가능합니다.

 

 

◇ 라이프 사이클의 3가지 대분류 유형

 

           ● 생성 될 때 : [마운팅]

           ● 업데이트 될 때 : [업데이팅]

           ● 제거할 때 : [언마운팅]

 

 

  리액트 활용해 개발을 하면,

   컴포넌트 단위로 UI를 화면에 보이게 하거나, 다른 UI,로 변경하거나, 현재 보이는 UI를 화면에서 제거 할 수 있습니다.

 

      ==> 따라서, 각 Component들을 생성(Mounting)하고, 업데이트(Updating)하고, 제거(Unmounting)하는 단계를

                       차례대로 겪는 생명주기(Life Cycle)를 가지고 있는 것 !!

 

                 ++ 효율적인 작업을 수행하기 위해서는 업데이트 과정 중간에 끼어들어서 데이터를 가공하기도 해야합니다.

 

                      +++ 라이프사이클에서 각 단계별로 필요한 순간에 필요한 작업들을 추가할 수 있는 매서드들이 존재합니다.

 

 

 

  ++ 함수 방식 라이프 사이클.

 

함수 방식 라이프 사이클

 


  ∇ 라이프 사이클 메서드.


 

    Ⅰ. 컴포넌트가 로딩되기 시작하는 Mount ( 생성 )

            : DOM이 생성되고, 웹 브라우저 상에서 나타나는 것을 뜻합니다.

 

 


 

              Ⅰ-1 . constructor ( 클래스 생성자 ).

                      : 클래스 인스턴스 객체를 생성하고 초기화하는 메서드로, 리엑트에서 컴포넌트를 만들 때 처음으로 실행.

 

                            √ 초기 state를 정할 수 있습니다.

                            √ 막 실행된 시점 -> 브라우저에서 우리가 작성한 JSX는 보이지 않음.

                            √ React 컴포넌트의 " constructor "는 해당 컴포넌트가 생성시에 호출.

 

                            √ " React.component " 를 상속한 컴포넌트의 생성자를 구현할 때는

                               다른 구문에 앞서서 super(props)를 호출해야합니다.

                                     -> 그렇지 않으면, this.props가 생성자 내에서 정의되지 않아 버그 발생.

 

                            √  " this.props " ,  " this.state " 에 접근이 가능하고 리액트 요소를 반환.

 

 

// constructor 기본 구조
class MyComponent() extends React.Component{
  constructor(props){
    super(props)
    //...
  }
}
// super 함수를 호출해야 React.Component class의 method가 호출 됩니다. 
// super를 호출하지 않으면 컴포넌트는 작동하지 않습니다.

 

          - 클래스형에서는 초기 state를 정할 때,  " constructor " 를 사용해야 합니다.

 

          - Hook에서는 " useState hook "를 사용하면, 초기 상태를 쉽게 설정 가능합니다.

 

          - 메서드를 바인딩하거나 state를 초기화하는 작업이 없다면,

                 해당 React 컴포넌트에는 " constructor "을 구현하지 않아도 됩니다.

 

 

// constructor 기본 구조
class MyComponent() extends React.Component{
  constructor(props){
    super(props)
    //...
  }
}
// super 함수를 호출해야 React.Component class의 method가 호출 됩니다. 
// super를 호출하지 않으면 컴포넌트는 작동하지 않습니다.
//class
class Example extends React.Component {
	constructor(props) {
      	super(props);
      	this.state = { count: 0 } ;
    }
}

// Hooks
const Example = () => {
  const [count,setCount] = useState(0);
}

 

              Ⅰ-2 . static getDerivedStateFromProp.

                      : props로 받아온 값을 state에 동기화시키는 용도로 사용.

                           - 컴포넌트가 마운트 될 때와 업데이트 될 때 호출.

//Class
class Example extends React.Component {
	static getDerivedStateFromProps(nextProps, prevState) {
      if (nextProps.value !== prevState.value) {
      return { value: nextProps.value}
      }
      return null
   }
}

                          - 정적 메서드이기 때문에,  this를 호출할 수 없습니다.

                          - 이유와 상관없이,  렌더링 때마다 매번 실행되므로 주의가 필요.

 

                          - 최초 마운트 시와 갱신 시,  render 메서드를 호출하기 직전에 호출됩니다.

                          - stats를 갱신하기 위한 객체를 반환하거나, null을 반환하여 아무것도 갱신하지 않을 수 있음.

 


 

              Ⅰ-3 . shouldComponentUpdate

                      : " props 혹은 state를 변경했을 때,  리렌더링을 할지 말지를 결정하는 메서드 " 입니다.

 

                           - 이 메서드에서는 반드시  true/false를 반환해야 합니다.

                                - > 기보적으로 true를 반환.

 

                                - > 조건에 따라 'false'를 반환하면, 해당 조건에는 render 함수를 호출하지 않습니다.

 

                         - 이 메서드는 "오직 성능 최적화만을 위한 것"이며,  렌더링 방지 목적을 사용하면 버그 발생.

 

//Class
class Example extends React.Component {
  shouldComponentUpdate(nextProps) {
    return nextProps.value !== this.props.value
  }
}

// Hooks
const Example = React.memo((prevProps, nextProps) => {
  return nextProps.value === prevProps.value
	}                        
)

 

 

             - Hook에선 보통 props는 React.memo //  state는 useMemo를 활용하면 렌더링 성능 개선 가능.

 

 


              Ⅰ-4 . 실제 로딩이 이루어지는 render

                      :  'reder' 메서드는 가장 기초적이면서 가장 중요한 메서드입니다.

                           - 컴포넌트 렌더링할 때 필요한 메서드 중 유일한 필수 메서드이기도 합니다.

 

                               - 최종적으로 component에서 작업한 결과물을 return하는 메서드.

                                  ( React Element는 보통 JSX를 사용하여 작성 )

 

                               - render 메서드가 실행되면서 JSX가 HTML로 변환되어서, 웹 브라우저 표시.!

 

 

                               - render 메서드는 컴포넌트가 로딩될 때에도 실행되지만,

                                                              컴포넌트의 데이터(state, props)가 업데이트 되었을 때에도 동작합니다. !

 

                               - render 메소드에서 setState나 props를 변화시키는 메소드는 가능하면 수행 X

                                    (잘못하면, 무한루프)

 

                               - render 메소드는 순수 함수여야 합니다.

                                      == input에 대해서 같은 output이 나와야한다는 것을 의미합니다.

 

    - 함수형 컴포넌트에서는 render를 안 쓰고 컴포넌트를 렌더링 가능합니다.

// Class
class Example extends React.Component {
  render() {
    return <div>컴포넌트</div>
  }
}

// Hooks
const example = () => {
  return <div>컴포넌트</div>
}
render() {
	return (
      <div>
        <header className="title">
          Yuntroll React
        </header>
      </div>
	);
}

 

    - 결과물로 나온 Element들이 Virtual DOM에 마운트되고, 실제 DOM에 업데이트됩니다.

 

 


              Ⅰ-5 . 처음 로딩이 끝난 뒤에 수행되는 componentDidMount

 

                           - Mounting의 마지막 부분. !

 

                           - render 메서드에 있는 모든 부분들이 브라우저에 나타나게 되었을 때만 실행되는 메서드.

                                == 컴포넌트를 만들고, 첫 렌더링을 마친 후에 실행합니다.

                                == " 초기 컴포넌트의 로딩 이후에 한번만 실행 " 되는 라이프 사이클 메소드.

 

                          - 여기서,  DOM을 직접 조작 가능합니다.

 

                                    √ D3, masonry 처럼 DOM을 사용해야 하는 외부 라이브러리 연동을 하거나,

 

                                    √ 컴포넌트에 필요한 데이터를 요청*수령하기 위해 axios,fetch 등을 통하여 ajax 요청을 하거나,

 

                                    √ DOM의 속성을 읽거나 직접 변경하는 작업을 진행합니다.

                                          == DOM이 렌더링 끝나기 전, DOM에 접근하면 오류가 발생하기에,

 

 

                            ○ 일반적으로 비동기 API를 사용합니다.

                            ○ 응답받은 외부 데이터는 내부 컴포넌트 상태에 저장되어 컴포넌트가 업데이트 되면

                                     render 메서드가 실행됩니다.

 

import React, { Component } from "react";

class UserListClass extends Component {
  state = {
    loading: true,
    users: [],
  };

  componentDidMount() {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then((response) => response.json())
      .then((users) => this.setState({ users, loading: false }));
  }

  render() {
    const { loading, users } = this.state;
    if (loading) return <div>Loading...</div>;
    return (
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    );
  }
}

 

 

                            ○ Hook에서는 useEffect를 활용하여 구현 가능.

// Class
class Example extends React.Component {
    componentDidMount() {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        ...
    }, []);
}

        + 여기서 useEffect의 []의존성 배열을 비워야지만 똑같은 메소드를 구현 가능합니다.

 

 


 

           

    Ⅱ . 컴포넌트의 업데이트 .

 

               ※ 컴포넌트의 업데이트는 4가지 상황에서 발생합니다. !

 

                     1. Props가 바뀔 때

                     2. State가 바뀔 때

                     3. 부모 컴포넌트가 리렌더링 될 때.

                     4. this.forceUpdate로 강제로 렌더링을 시도하는 경우.

 

                     ->> input이 달라지니, 당연히 output도 달라져야 하기 때문

                     ->> 상위 component가 update되면 하위 component도 rendering 되고 다시 mount 됩니다.

 


 

              Ⅱ-1 . componentwillReceiveProps

 

                           - 컴포넌트가 "새로운 속성을 전달받을 때 실행 " 됩니다.

 

                           - 컴포넌트가 새로운 속성을 받아오는 시점에 끼어들어서,

                               render 메서드를 호출하기 전에 일부 로직을 추가 가능합니다.

 

                           - 해당 메서드는 새로운 속성을 인자로 받습니다.

                           - 컴포넌트를 최초로 실행할 때는 렌더되지 않습니다.

                           - setState 메서드를 호추해도, 추가로 다시 렌더링이 발생하지 않습니다.

 

                           - 속성 값의 변경과 상관없이  재렌더링이 이루어질 때마다 실행 합니다.

                           - newProps가 항상 현재 속성과 다른 값이라고 가정할 수 없습니다.

 

componentWillReceiveProps(newProps) {
  this.setState({
      opacity: (newProps.isVisible) ? 1 : 0
  })
}

 

 

 


         Ⅱ-2. shouldComponentUpdate

                      : " props 혹은 state를 변경했을 때,  리렌더링을 할지 말지를 결정하는 메서드 " 입니다.

 

                           - 이 메서드에서는 반드시  true/false를 반환해야 합니다.

                                - > 기보적으로 true를 반환.

 

                                - > 조건에 따라 'false'를 반환하면, 해당 조건에는 render 함수를 호출하지 않습니다.

 

                         - 이 메서드는 "오직 성능 최적화만을 위한 것"이며,  렌더링 방지 목적을 사용하면 버그 발생.

 

 


 

             Ⅱ-3 . getSnapShotBeforeUpdate                  

      

                           - 해당 메서드는, 'render'에서 만들어진 결과가 브라우저에 실제로 반영되기 직전에 호출됩니다.

 

                           - 업데이트 되기 직전에,  'snapshot(props & states)을 확보하는게 목적.

                           - 인자값에 반환되는,  'prevProps & prevState ' 는 변경되지 전에 값을 나타냅니다.

 

getSnapshotBeforeUpdate(prevProps,prevState){
   // DOM 업데이트가 일어나기 직전의 시점입니다. 
   return {};
}

 

                 

                           - 컴포넌트에 변화가 일어나기 직전의 DOM 상태를 가져와서 특정 값을 반환하면 

                                  그 다음 발생하게 되는 componentDidUpdate 함수에서 받아와서 사용합니다/

class Example extends React.Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current
      return list.scrollHeight - list.scrollTop
    }
    return null
  }
}

 

 

 


 

             Ⅱ-4 . componentwillupdate

 

                           - 컴포넌트의 변경이 완료되었을 때 수행되는 메소드,

                                  - 조금 복잡한 작업을 수행하기에 최적화.

                                  - 처음 렌더링 될 때는 실행되지 않고,  리 렌더링을 완료한후 실행.

                                        == state나 props가 변경되더라도 DOM 변화가 발생한 뒤 발생된다는 것                      

 

                           - render 메소드가 실행되어 업데이트 된 state & props 와 

                                업데이트 되기 전인 state, props를 가지고 비교 작업을 가능하게 해줍니다.

                                [ 업데이트 끝난 직후이르모,  DOM 관련 처리를 해도 무방. ]

 

                            - prevProps 또는 prevState를 사용하여 컴포넌트가 이전에 가졌던 데이터에 접근이 가능합니다.

 

 

 

 


 

 

    Ⅲ . 컴포넌트의 삭제  Unmounting(제거)

 

         ※ Unmounting은 DOM에서 제거되는 것을 뜻합니다. JSX에 포함되었다가 이후에 제거되는 경우에 발생.

return (
	<div>
		{ value ? <Comp1 /> : <Comp2 /> }
		<button onClick={()=>setValue(!value)}>Change</button>
	</div>
);

초기에 value ture인 상황에서 버튼을 클릭하면 Comp1은 언마운트되고 Comp2는 마운트 됩니다.

또 버튼을 클릭하면 Comp2는 언마운트되고 Comp1은 마운트됩니다.

 

<Comp1 key={value} />

key가 변하는 경우도,  언마운트 됩니다.

 

 

 

             Ⅲ-1 . ComponentWillUnmount

                     - 이 메서드는 컴포넌트를 DOM에서 제거할 때 실행합니다. 컴포넌트가 사라질 때 수행되는 메소드.

 

                    -  componentDidMount에서 등록한 이벤트가 있다면 여기서 제거 작업을 해야합니다.

                       - >> 타이머를 제거하거나, DOM 요소를 정리하거나,

                                componentDidMount에서 연결한 이벤트를 제거할 수 있습니다.

 

                      -  컴포넌트가 화면에서 사라지기 직전에 호출합니다.

 

                     - 외부 라이브러리를 사용한게 있거나, 해당 라이브러리에 dispose 기능이 있다면 여기서 호출합니다.

                     -  함수형 컴포넌트에서는 useEffect CleanUp 함수를 통해 해당 메서드를 구현할 수 있습니다.

// Class
class Example extends React.Component {
    coomponentWillUnmount() {
        ...
    }
}

// Hooks
const Example = () => {
    useEffect(() => {
        return () => {
            ...
        }
    }, []);
}