React.js

최광일·2023년 2월 4일
0

React 홈페이지리뷰

What it is


사용자 인터페이스 구축을 위한 JavaScript 라이브러리

  • 선언적
    • React가 데이터가 변경될 때 올바른 컴포넌트만 효율적으로 업데이트하고 렌더링합니다.
  • 컴포넌트 기반
    • 상태를 관리하는 캡슐화된 컴포넌트를 빌드한 다음 이를 구성하여 복잡한 UI를 만듭니다.

선언적인 것과 컴포넌트 기반인 것이 왜 중요한지 알기위해 다음의 예제를 살펴보자.

위의 예제는 간단한 문구와 카운터를 포함한다.

이를 바닐라 javascript로 구현한다면 다음과 같을 수 있다.

<div id="app"></div>
<script type="text/javascript">
  const headerElement = document.createElement('div');

  const textElement = document.createElement('h1');
  const textElementText = document.createTextNode('Thanks React! 🚀');
  textElement.appendChild(textElementText);
  headerElement.appendChild(textElement);
 
  const button = document.createElement('button');
  let likes = 0;
  const buttonText = document.createTextNode(`Likes ${likes}`);
  button.appendChild(buttonText);
  button.addEventListener('click', () => {
    likes += 1;
    buttonText.nodeValue = `Likes ${likes}`; // DOM node를 직접 변경
  });
  headerElement.appendChild(button);
 
  const app = document.getElementById('app');
  app.appendChild(headerElement);    
</script>

온라인 코드 에디터에서 위의 코드로 예제를 확인해 볼 수 있다.

라이브러리 없이 자바스크립트만으로 인터랙티브 앱을 만든다면 DOM API를 사용하여 명령적으로 구현해야 한다.

엘리먼트를 만들자!

  • 엘리먼트를 만든다.
  • 텍스트 노드를 만든다.
  • 엘리먼트에 텍스트 노드를 추가한다.
  • 엘리먼트에 이벤트 핸들러를 추가한다.
  • ...

만약 앱의 크기가 커지고 상호작용이 많아진다면 직접 DOM API를 사용하여 처리하는 것은 힘들어지고 비효율적이게 된다.

선언적

이런 문제점을 해결하고자 리액트는 선언적으로 UI를 관리한다

<div id="app"></div>
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script type="text/javascript">
  const textElement = React.createElement('h1', null, 'Thanks React! 🚀');
  const buttonElement = React.createElement(() => {
    const [likes, setLikes] = React.useState(0); // DOM node를 알아서 변경
    return React.createElement('button', { onClick: () => { setLikes(likes + 1) }}, `Likes ${likes}`);
  });
  const headerElement = React.createElement(React.Fragment, null, [textElement, buttonElement]);
  
  const app = document.getElementById('app');
  ReactDOM.render(headerElement, app);
</script>

리액트는 createElement(type, props, children) 함수를 통해 UI를 선언적으로 처리하게 해준다.

엘리먼트를 만들자!

  • 리액트야 엘리먼트를 알아서 잘 만들어줘
  • type, props, children은 다음과 같아
  • 고마워!

뿐만 아니라, render(reactNode, domNode) 함수를 통해 데이터가 변경될 때 올바른 컴포넌트만 효율적으로 알아서 업데이트한다.

이를 통해, 개발자는 데이터가 변경되는 경우에 UI를 변경하는 것을 직접 관리하지 않게 된다.

컴포넌트 기반

또한, 리액트는 JSX를 사용한 컴포넌트 기반으로 복잡한 UI를 만든다

JSX란 Javascript를 확장한 문법으로, HTML과 javascript를 함께 표현하도록 한다.

<div id="app"></div>
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script type="text/javascript">
  const Header = () => {
    const [likes, setLikes] = useState(0);
    return (
      <>
        <h1>Thanks React! 🚀</h1>
        <button onClick={() => setLikes(likes + 1)}>
          Likes ({likes})
        </button>
      </>
    );
  }
 
  const HomePage = () => {
    return (
      <>
        <Header />
        <Header />
      </>
    );
  }
  
  const app = document.getElementById('app');
  ReactDOM.render(<HomePage />, app);
</script>

캡슐화된 컴포넌트를 통해 HTML, CSS, Javascript 코드를 한 위치에서 관리할 수 있으며, 코드의 재사용성을 높일 수 있다.

How it works


리액트는 어떻게 데이터가 변경될 때 마다 올바른 컴포넌트만 효율적으로 업데이트하고 렌더링할까?

리액트의 라이프사이클을 이해하여 이를 알아보자.

위의 사진을 보면 리액트의 라이프사이클에는 2가지 phase가 존재하며, 각각의 역활은 다음과 같다.

  • 1 Render
    • Component를 Virtual DOM으로 변환한다
    • 이전 Virtual DOM과 현재 Virtual DOM을 비교한다
    • 이를 통해, 변화가 일어난 Component를 확인한다
  • 2 Commit
    • 변화가 일어난 Component의 Viritual DOM을 통해 Real DOM으로 업데이트 한다.

Virtual DOM 이라는 새로운 개념이 나타났다. 2가지 phase를 살펴보며 이에 대해 알아본다.

Render

위의 예시에서 살펴본 textElement 를 확인해보면, 우리가 createElement 함수로 정의한 type, props 등을 확인할 수 있다.

// textElement Component
const textElement = React.createElement('h1', null, 'Thanks React! 🚀');

// textElement Virtual Dom
{
  $$typeof: Symbol(react.element)
  key: null
  props: {children: 'Thanks React! 🚀'}
  ref: null
  type: "h1"
  _owner: null
}

이와 같이, Virtual DOM이란 javascript의 Object로 DOM을 표현한 것이다.

Render phase에서는 이처럼, 우리가 작성하는 컴포넌트의 JSX를 Virtual DOM으로 변환한다.

리액트가 효율적으로 DOM을 업데이트 한다는 것은 이 Virtual DOM을 활용하기 때문이다.

DOM에서 직접 변경사항을 적용하는 것이 아니라, 렌더링 시점에 Virturl DOM
을 생성하여 이전 렌더링 시점에 생성된 Virtual DOM과 비교한다.

리액트에서는 이를 Reconcilation이라고 부르며, 비교하는 알고리즘은 다음과 같다.

  • 1 엘리먼트 타입이 다른 경우
    • 해당 엘리먼트의 자식 노드를 모드 unMount 한 뒤 다시 mount 한다.
  • 2 엘리먼트 타입이 같은 경우
    • 변경된 속성이 있는 경우, 속성만 변경해준다.
  • 3 컴포넌트 엘리먼트 타입이 같은 경우

그리고 위의 과정은 렌더링이 발생한 컴포넌트의 하위 컴포넌트로 재귀적으로 이루어진다.

리액트는 이처럼 top-down 방향의 one way data flow를 가진다.

리액트에서 이러한 렌더링의 트리거는 위의 라이프사이클 사진에서와 같이 세 가지가 존재한다.

  • New props
  • setState
  • force update

Commit

Render phase에서 virtual DOM을 통해 변경이 일어난 컴포넌트를 확인했다면, Commit phase에서는 DOM에서 변경이 일어난 컴포넌트들과, 해당 컴포넌트들의 자식 컴포넌트들을 업데이트 시켜준다.

Optimization


리액트 렌더링 라이프사이클을 보면, 렌더링은 다음의 경우에 트리거된다.

  • 1 New Props
  • 2 setState()
  • 3 forceUpdate()

렌더링이 트리거되는 경우에 다음과 같은 불필요한 리렌더링이 발생할 수 있으며, 각각의 최적화 방법은 다음과 같다.

  • 1 New Props
    • 렌더링은 변경이 일어난 컴포넌트의 자식 컴포넌트로 재귀적으로 진행된다.
    • 자식 컴포넌트는 shouldComponentUpdate() 를 통해 props 변경 여부를 확인한다.
    • 이때, props는 Object.is()으로 shallow comparison을 하므로 기본값은 true이다.
    • true인 경우(props가 변경된 경우) vDOM을 생성하여 이전 vDOM과 vDOMeq를 확인한다
    • 따라서 자식 컴포넌트의 불필요한 렌더링을 제어하기 위해 shouldComponentUpdate() 를 false 시킨다
  • 2 setState
    • 렌더링은 변경이 일어난 컴포넌트에서 진행된다.
    • 변경이 일어난 컴포넌트에서 shouldComponentUpdate() 를 통해 state 변경 여부를 확인한다.
    • 따라서, 원하는 상태만 변경될 수 있도록 상태관리가 필요하다 (상태의 불변성 유지, 올바른 상태 구조, 올바른 상태 공유, ...)
  • 3 forceUpdate by effect & event
    • sideEffect는 렌더링 과정에서 실행되지 않아야 한다.
    • 따라서, sideEffect는 렌더링 과정에서 실행되지 않는 event handler로 처리하라.
    • 그러면, sideEffecr로 인해 렌더링이 방해받지 않는다.
    • 그러므로, useEffect는 렌더링 과정에서 실행되기 때문에 최후의 수단으로 사용하라.

  • 1 C1
    • node가 SCU true여서 vDOMEq 해봤더니 달라서 Reconcilation needed
  • 2 C2, C3
    • C1의 자식 node인 C2와 C3의 new Props로 인한 렌더링 여부 확인
    • C2는 SCU false여서 No Reconcilation needed
    • C3는 SCU가 true이며 vDOMEq 해봤더니 달라서 Reconcilation needed
  • 3 C4, C5
    • C2는 SCU false여서 No Reconcilation needed
    • C2의 자식 node인 C4, C5 No Reconcilation needed
  • 4 C6, C7, C8
    • C3의 자식 node인 C6와 C7, C8의 new Props로 인한 렌더링 여부 확인
    • C6은 SCU가 true이며 vDOMEq 해봤더니 달라서 Reconcilation needed
    • C7은 SCU false여서 No Reconcilation needed
    • C8은 SCU가 true이며 vDOMEq 해봤더니 같아서 No Reconcilation needed

0개의 댓글