[React.js]리액트 만들기.part1.jsx-rendrer

유선향·2025년 10월 3일

<React>

목록 보기
2/5
post-thumbnail

바닐라 자바스크립트로 직접 리액트의 주요 기능을 만들어보며, 동작원리를 좀더 심층적으로 이해하기 위해 아래 글을 따라가며 얻은 인사이트와 학습을 기록하는 글입니다. 또한 아래 글의 4가지 챕터로 리액트의 동작원리를 이해하는 단계를 그대로 따라가기에, 아래 Medium 글을 참고하시는게 직접 해보시는 것에는 도움이 되실 겁니다.

Let’s build a React from scratch: Part 1 — VirtualDOM and Renderer

git hub repo : part1 branch(파트에 따라서 브랜치를 분배 했습니다)


환경 셋업

평소에는 프레임워크 생성 명령어로 프로젝트를 처음에 만들었어서 그런지 아무것도 들어있지 않은 IDE 환경... 낯설다...

기본 셋업

typescript 설치와,ts.config.json 설정, script 에는 dev 명령어에 타입 검사후 컴파일링 하게끔 작성 했다.

  • 난 위의 글과 다르게, 기존에 익숙한 src 폴더를 만들고, src 하위에 폴더에 타입검사가 실행되게끔 했다.
    당연하게도 index.html의 스크립트 부분에는 타입 스크립트 컴파일링 이후에 생성된 app.js 가 들어가야 한다.

ts.config.json

보통 템플릿 생성 명령어로 프로젝트를 생성하다보면, typescript 를 사용하시겠습니까? 라고 물어보고, 그렇겠다고 하면 자동으로 해당 템플릿에 권장되는 내용의 설정이 적용된 ts.config.json 파일이 자동으로 생성되고, 대부분은 그 파일의 exclude, include, rootdir 등의 부분만 수정할뿐 이후로 수정을 잘 하지 않는다. Medium 글에서의 설정을 따르고 아래와 같은 경고가 떴는데....

이는 당연히 ts.config.json 파일에서 jsx:'react' 라고 설정했기 때문일거다.


리액트 공식문서의 typescript 추가하기 문서
jsx 설명서를 참조하세요... 들어가봤다.

위의 Medium 글에도 나와있는 내용이 나와있다!

app.tsx
export const HelloWorld = () => <h1>Hello world</h1>; 

이렇게 작성한 jsx 파일을,

//ts.config.json 에서 jsx:"react-jsx" 일때 
import { jsx as _jsx } from "react/jsx-runtime";
export const HelloWorld = () => _jsx("h1", { children: "Hello world" });

//ts.config.json 에서 jsx:"react" 일때
import React from 'react';
export const HelloWorld = () => React.createElement("h1", null, "Hello world");
 

jsx:'react'일때는 React.createElement 를 통해 해당 요소의 태그와 props, chidren을 가지고 화면을 구성하는데 사용된다는 것을 유추할수 있다.
결국 createElement 라는 함수는 아래 임의로 작성한 console.log() 대신 어떠한 행동을 수행할 것이다.

실제로 공식문서-react.createElement 에서도 해당 내용을 살펴 볼 수 있다.


가상돔

리액트의 가상돔은 단지 아래 처럼 보이는 객체일 뿐이다. 버츄얼 돔이라는 말이 거창해보이지만, 브라우져의 돔트리를 흉내낸 것이 버츄얼 돔이라는 표현을 생각해보면, 가상돔은 단지 브라우져의 전역객체의 모습을 흉내낸 모양일 것이니, 아래같은 단순한 전역객체에서는 허무해 보일 수도 있겠다.

//app.tsx
const React = {
  createElement: (tag, props, ...children) => {
    const el = {
      tag,
      props,
      children,
    };
    console.log(el);
    return el;
  },
};

const App = (
  <div draggable>
    <h2>Hello React!</h2>
    <p>I am a grimza99</p>
    <input type="text" />
  </div>
);


//타입검사를 마친 app.js
var React = {
    createElement: function (tag, props) {
        var children = [];
        for (var _i = 2; _i < arguments.length; _i++) {
            children[_i - 2] = arguments[_i];
        }
        var el = {
            tag: tag,
            props: props,
            children: children,
        };
        console.log(el);
        return el;
    },
};
var App = (React.createElement("div", { draggable: true },
    React.createElement("h2", null, "Hello React!"),
    React.createElement("p", null, "I am a grimza99"),
    React.createElement("input", { type: "text" })));


render

  • 리액트가 화면을 렌더할때,getElemtenById()로 마운트될 위치를 인자로 받는다는 것은 이미 템플릿 생성 명령어로 리액트 프로젝트를 생성한뒤, main.js파일을 들어가본 사람이라면 알고 있을 것이다.
//템플릿 생성 명령어로 만들어진 프로젝트의 main.jsx
import ReactDOM from "react-dom/client";
import App from "./App.jsx";

ReactDOM.createRoot(document.getElementById("root")).render(
  <>
    <App />
  </>
);
//app.tsx
const React = {
  createElement: (tag, props, ...children) => {
    if (typeof tag === "function") {
      return tag(props, ...children);
    }
    const el = {
      tag,
      props,
      children,
    };
    return el;
  },
};

const render = function (el, container) {
  let domEl;
  // 1. el의 유형을 확인한다.

  if (typeof el === "string") {
    // 문자열인 경우 텍스트 노드처럼 처리해야 함.
    domEl = document.createTextNode(el);
    container.appendChild(domEl); // 텍스트에 대한 자식이 없으므로 반환
    return;
  }
  // 2. 먼저 el에 해당하는 문서 노드를 만든다.
  domEl = document.createElement(el.tag);

  // 3. domEl에 props를 설정한다.
  let elProps = el.props ? Object.keys(el.props) : null;
  if (elProps && elProps.length > 0) {
    elProps.forEach(function (prop) {
      return (domEl[prop] = el.props[prop]);
    });
  }
  // 4. 자식을 만든다.
  if (el.children && el.children.length > 0) {
    // child가 렌더링되면 컨테이너는 여기서 생성한 domEl이 된다.
    el.children.forEach(function (node) {
      return render(node, domEl);
    });
  } // 4. DOM 노드를 컨테이너에 추가한다.
  container.appendChild(domEl);
};

const App = () => {
  return (
    <div draggable>
      <h2>Hello React!</h2>
      <p>I am a grimza99</p>
      <input type="text" />
    </div>
  );
};
render(<App />, document.getElementById("myapp"));

이렇게 리액트의 렌더링 사고를 흉내낸 아주 빈약한? 리액트 프로젝트가 완성되었다. 물론, 이것은 리액트의 초기 렌더링의 과정을 흉내냈을뿐, 리액트 파이버가 상태 변화를 감지하여 최소한의 재렌더링을 만들어 내는 과정이나, useState 같은 훅은 존재 하지 않는다. useState 훅은 다음 챕터에서 다뤄본다.

0개의 댓글