리액트 동작 원리 파트1: 리액트 요소 트리를 만드는 과정

Plato·2022년 8월 12일
0

서론

리액트는, 리액트 요소 트리를 만들고, 리액트 요소 트리를 비교하여, 실제 DOM을 어떻게 수정할지 결정한다. 그렇기에, 리액트 요소 트리를 이해하는 것이 중요한데, 리액트 요소 트리를 만드는 과정을 같이 살펴보자.

본론

단어 설명

리액트 컴포넌트와 인스턴스 그리고 요소는, 리액트 요소 트리를 만드는 과정을 이해하기 위해, 알아야 하는 개념이다. 이미, 해당 개념을 접하고 다뤄봤다고 가정한 상태에서, 짧게 설명하겠다.

컴포넌트

추상적으로 컴포넌트는, '재사용과 디버깅의 용이성을 위해, 연관 있는 html, css, javascript의 코드를 묶어 만든 대상'이다. 리액트에서는 추상적인 개념인 컴포넌트를, '리액트 요소를 출력으로 갖는 클래스와 함수'로 구현했다.
클래스형 컴포넌트는, React.Component 클래스를 상속받는 클래스이며, 리액트 요소를 반환하는 render 메소드를 갖는다. 함수형 컴포넌트는, props를 입력으로 갖고, 리액트 요소를 출력으로 갖는 함수다.

function NameCard({nameCardInfo}) {
  const {company, team, name, phoneNumber, email} = nameCardInfo
  return (
    <div className='name-card'>
      <p>
        company: {company}
        team: {team}
        name: {name}
        phoneNumber: {phoneNumber}
        email: {email}
      </p>
    </div>
  )
}

위의 코드 조각에서, NameCard는 함수형 컴포넌트다. JSX를 반환하지만, 이를 babel이 리액트 요소로 바꿔주기 때문에, 리액트 요소를 반환하는 아래의 코드 조각과 동일하다.

function NameCard({nameCardInfo}) {
  const {company, team, name, phoneNumber, email} = nameCardInfo
  const innerParagraph = React.createElement('p',{},
    `
    company: ${company}
    team: ${team}
    name: ${name}
    phoneNumber: ${phoneNumber}
    email: ${email}
    `)
  return ( 
    React.createElement('div', {
      class: 'name-card'
    }, innerParagraph)
  )
}

인스턴스

클래스형 컴포넌트의 경우, 각기 다른 state와 props를 갖는 인스턴스를 만들 수 있다. 인스턴스가 유용한 이유는, 하나의 컴포넌트가 하나의 형태로, 한 번만 사용되는 건 아니기 때문이다. 상황에 맞는 상태와 props를 갖는 인스턴스를 여러 개 만듦으로써, 다양한 상황에 알맞게 컴포넌트를 재사용할 수 있고, 변화하도록 구현할 수 있다.
함수형 컴포넌트의 경우, 인스턴스는 존재하지 않는다. 하지만, hook을 사용하여, 마치 인스턴스가 존재하는 것처럼, state를 다루고 생애 주기에 맞춰, 원하는 작업을 수행할 수 있다. 그래서 이 글에서는, 함수형 컴포넌트던 클래스형 컴포넌트던, 마치 인스턴스가 존재하는 것처럼, '컴포넌트의 인스턴스'라는 표현을 사용할 것이다.

리액트 요소

리액트 요소는, html 요소와 리액트 컴포넌트 인스턴스를 설명하는 자바스크립트 객체다. 여기서 '설명'한다는 말의 의미부터 알아보자. 자바스크립트 객체인 만큼, 리액트 요소는, 데이터를 담는 자료구조다. 무엇을 담는 걸까? 설명하고자 하는 대상 컴포넌트 인스턴스에 대한 정보, 혹은 대상 html 요소에 대한 정보를 담는 것이다. 너무 추상적이니, 예시를 통해 자세히 설명하겠다.
1. html 요소를 설명하는 리액트 요소와
2. 리액트 컴포넌트를 설명하는 리액트 요소의 예시를 살펴보자.

html 요소를 설명하는, 리액트 요소
function NameCard({nameCardInfo}) {
  const {company, team, name, phoneNumber, email} = nameCardInfo
  return (
    <div className='name-card'>
      <p>
        company: {company}
        team: {team}
        name: {name}
        phoneNumber: {phoneNumber}
        email: {email}
      </p>
    </div>
  )
}

위 코드 조각을 언뜻보면, NameCard 함수는 html 요소를 반환하는 것처럼 보일 수 있다. NameCard 함수가 무엇을 반환하는지, 콘솔로 프린트해보자.

Object
  $$typeof: Symbol(react.element)
  key: null
  props: {className: 'name-card', children: {}}
  ref: null
  type: "div"
  [[Prototype]]: Object

이때, 반환되는 값은 html 태그가 아니라, 자바스크립트 객체인 점에 주목하자. 리액트 요소는, html 요소나 컴포넌트 인스턴스가 아니라, 해당 태그 혹은 컴포넌트 인스턴스를 묘사하는, 자바스크립트 객체에 불과하다.
여기서 주목할 프로퍼티는, props와 type이다.
반환하는 리액트 요소는, html 태그인 div를 설명하고 있기 때문에, type에는 문자열 "div"가 들어간다. 그리고 props에는, div의 attribute가 담긴다. 그리고 props.children에는, div html 요소의 자식인, p html 요소를 설명하는 리액트 요소가 담긴다.

컴포넌트를 설명하는, 리액트 요소
function App() {
  return (
    <PickButton nameCardData = {nameCardData}></PickButton>
  )
}

위의 코드 조각의 App 컴포넌트 함수가 반환하는 값을, 콘솔에 프린트하면 아래와 같이 나온다.

Object
  $$typeof: Symbol(react.element)
  key: null
  props: {nameCardData: Array(10)}
  ref: null
  type: ƒ PickButton(_ref)
  [[Prototype]]: Object

이 리액트 요소는, PickButton 컴포넌트의 인스턴스를 묘사하고 있기 때문에, type 프로퍼티가 PickButton 함수를 참조한다.
컴포넌트를 묘사하기 때문에, props에는 attribute가 아니라, 컴포넌트 인스턴스의 props가 담기게 된다.

이처럼, '설명하고자 하는 대상이, 어떤 attribute/props를 갖는지, 어떤 컴포넌트의 인스턴스인지 혹은 어떤 html태그 요소인지 등등을 담는, 자바스크립트 객체'가 리액트 요소다.

리액트 요소 트리를 만드는 과정

리액트 요소가 무엇인지 살펴봤으니, 컴포넌트들이 반환하는 리액트 요소로, 리액트 요소 트리를 구성하는 과정을 살펴보자.
일반적으로 사용하는 App 컴포넌트부터 시작하자.

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

Create React App을 사용하면, 위와 같은 코드를 접하게 된다.
여기에서 <App /> jsx 라인은, App 컴포넌트의 인스턴스를 설명하는 리액트 요소로 바뀌게 된다. 이를 콘솔에 프린트한 결과는 아래와 같다.

// console.log(<App />)의 결과
Object
  $$typeof: Symbol(react.element)
  key: null
  props: {}
  ref: null
  type: ƒ App()
  [[Prototype]]: Object

type 프로퍼티에 App 함수가 담긴다. react는 이 객체의 type 프로퍼티가 참조하는, App 함수를 실행시켜서, 더 구체적인 리액트 요소 트리를 만든다. App 함수를 살펴보자.

function App() {
  return (
    <PickButton nameCardData = {nameCardData}></PickButton>
  )
}

App 함수는, 다시 PickButton 리액트 요소를 반환하고, 리액트는 다시 PickButton 함수를 실행하여 새로운 리액트 요소를 반환 받는다. 반환하는 리액트 요소가, html 요소를 설명하는 리액트 요소일 때 까지, 이 과정을 재귀적으로 반복하여, 전체 리액트 요소 트리를 만든다.

참고 자료

https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html
https://www.youtube.com/watch?v=7YhdqIR2Yzo
https://reactjs.org/docs/reconciliation.html

0개의 댓글