본문 바로가기
Programming/Javascript

자바스크립트로 만들어 보는 리액트 프레임워크 - 2. jsx로 화면 렌더링

by peter paak 2021. 6. 21.
728x90

이제 jsx를 이용하여 리액트와 비슷한 형태의 프레임워크를 만들어 보겠습니다.

  1. 자바스크립트로 만들어 보는 리액트 프레임워크 - 1. 기본 컨셉
  2. 자바스크립트로 만들어 보는 리액트 프레임워크 - 2. jsx
  3. 자바스크립트로 만들어 보는 리액트 프레임워크 - 3. hook
  4. 자바스크립트로 만들어 보는 리액트 프레임워크 - 4. redux
  5. 자바스크립트로 만들어 보는 리액트 프레임워크 - 5. thunk

모든 소스 코드는 github를 참고해주시기 바랍니다

jsx

jsxJavascript XML의 약자로 자바스크립트로 html 태그와 같은 마크업 형태를 표현할 수 있습니다.

function App() {
  return (
    <div className="a">안녕하세요</div>
  )
}

하지만 우리는 앞서 element만을 이용하여 충분히 화면에 잘 표현을 할 수 있었습니다.
그렇다면 리액트에서는 왜 jsx을 사용할까요?

우리는 앞서 element라는 것을 사용하여 노드를 생성하여 dom에 표현을 했습니다.

하지만 표현하고자 하는 html이 더 복잡하다면 어떻게 될까요?

아마 이런 간단한 html을 표현하는데 우리는 저정도 길이의 element 객체를 생성해야 될 것입니다. 정말 복잡한 페이지라면 우리가 감당하기 힘들정도로 복잡한 element를 개발자 스스로가 다뤄야 될 것입니다.

그래서 필요한 것이 jsx입니다

jsx는 html 태그로 표현하면 자동으로 element 객체롤 만들어 줍니다

왼쪽은 jsx로 작성된 html 태그 형식입니다.
jsx는 html이 아니라 자바스크립트입니다. 이해를 위해 html 태그 형식이라고 칭했습니다.
이를 보여주는 것이 바로 div 태그의 className입니다. 자바스크립트에서는 html의 class를 className로 사용합니다. (그래서 리액트에서는 className을 사용합니다)
이제 우리는 아무리 긴 코드라도 jsx만 생성하면 element를 얻을 수 있습니다. 앞서 element로 dom에 렌더링해보았습니다. 결국 jsx로 코드를 작성하면 자동으로 element를 얻게 되고 우리는 이것을 dom에 렌더링시키기만 하면 됩니다.

jsx 동작

jsx는 자바스크립트가 공식적으로 제공하는 문법이 아닙니다. 그래서 babel이라는 transpiler를 통해 브라우저가 이해할 수 있는 버전의 자바스크립트로 변경해야 합니다.
이제 jsx가 컴파일 됬을 때 어떻게 생겼는지 살펴보겠습니다. 이곳에서 확인 가능합니다.

먼저 transpile되기 전 jsx를 살펴보겠습니다.

/** @jsx createElement */
<div className="a">Hello</div>

앞서 설명한 것과 같은 jsx 형태입니다. 단지 차이가 있다면 /** @jsx createElement */ 라는 부분입니다.

이 부분은 내부적으로 babel의 transpiler가 @jsx를 찾아내고 바로 따라오는 createElement라는 이름 함수를 호출하여 element를 생성하겠다는 것을 뜻합니다. 함수 이름은 우리가 마음대로 정할 수 있습니다.

변환 이후를 보면 이해하기 조금 더 쉽습니다. 바벨로 변환 이후의 jsx를 보겠습니다.

createElement("div", {className: "a"}, "Hello");

babel이 tranpile하면 우리가 아는 자바스크립트 형식으로 변환됩니다. jsx의 정보들이 createElement의 파라미터로 들어가 호출됩니다.

createElement(type, props, children)

  1. type
    • 첫번째 파라미터는 type입니다.
    • html의 태그입니다.
    • "div", "a" 혹은 함수가 될 수 있습니다. (함수의 경우는 함수형 컴포넌트를 뜻합니다)
  2. props
    • 두번째 파라미터는 properties입니다
    • html의 프로퍼티입니다
    • "class", "id", "onclick" 등이 오게 됩니다.
  3. children
    • 세번째 파라미터는 children입니다
    • 자식 노드 혹은 텍스트가 될 수 있습니다.

jsx는 이런 규칙을 통해 element를 생성할 수 있습니다.

그렇다면 jsx를 통해서 createElement라는 함수를 호출하는 것도 알겠는데, element를 생성하지는 않는데? 라고 생각하실 수 있습니다. 정확히 설명하면 jsx는 element를 생성하기 위한 함수를 호출해줍니다. 즉, 개발자가 element를 생성하는 createElement라는 함수를 정의하면 jsx가 그 함수를 호출해줍니다.

function createElement(type, props, ...children) {

    return {
        type,
        props: {
            ...props,
            children
        }
    }
}

tag, props, children을 파라미터로 받는 createElement 함수를 정의합니다. 해당 함수는 element를 반환하게 됩니다.

jsx 동작 정리

정리하면 다음과 같습니다.

  1. 생성한 jsx를 바벨이 transpile
  2. transpile된 코드는 createElement 함수를 호출
    • createElement는 개발자가 정의
  3. createElement가 element 반환

여기까지가 jsx로 element를 반환하는 것을 보았습니다. 이후 프로세스는 이전에 설명한 자바스크립트로 만들어 보는 리액트 프레임워크 - 1. 기본 컨셉 과 동일합니다. element 객체로 노드를 생성하고 dom에 렌더링하면 끝입니다.

이제 실제로 구현해 보겠습니다.

만들어보기

우리는 총 4단계로 진행할 예정입니다.

  1. App 컴포넌트 생성
  2. createElement 함수 정의
  3. render 함수 호출
  4. render 함수 생성

진행하기 앞서 index.html의 헤더에 아래 스크립트를 추가해 줍니다.

<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.js"></script>

앞서 jsx를 일반 자바스크립트로 변환하기 위해 필요한 바벨을 사용합니다. (참고로 리액트에서는 @babel/plugin-transform-react-jsx를 사용합니다. 내부의 dependency로 @babe/plugin-sytax-jsx와 @babel/helper-plugin-utils를 이용하여 transpile합니다)

추가적으로 사용하는 자바스크립트 파일을 아래와 같이 불러옵니다. 바벨을 사용하기 위해 type="text/babel"을 사용했습니다.

<script type="text/babel" src="step1.js"></script>

index.html 전체코드는 다음과 같습니다.

<html>
    <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.js"></script>
    </head>
    <body>
        <div id="root"></div>
        <script type="text/babel" src="step2.js"></script>
    </body>
</html>

그럼 이제 자바스크립트 코드로 넘어가보겠습니다

1. App 컴포넌트 생성

/** @jsx createElement */
function App() {
    return (
        <div className="a">안녕하세요</div>
    );
}

App 컴포넌트는 리액트 컴포넌트의 모양을 따르고 있습니다. div 태그 부분이 바로 jsx 문법을 사용하고 있습니다. jsx를 브라우저가 이해하는 자바스크립트로 변환하기 위해 /**@jsx createElement */ 를 사용했습니다.앞서 바벨 스크립트 태그가 변환을 해줍니다.

2. createElement 함수 정의

이제 createElement 함수를 정의해보겠습니다. 파라미터를 각각 tag, props, children으로 생성하고 반환값을 element 형식에 맞춰 반환해줍니다. 입력값과 출력값을 알기위해 log를 찍어보았습니다.

function createElement(type, props, ...children) {
    console.log("createElement 호출",type, props, children)

    return {
        type,
        props: {
            ...props,
            children
        }
    }
}

그리고 테스트용으로 앞서 생성한 App 컴포넌트를 호출해봅니다

/** @jsx createElement */
function App() {
    return (
        <div className="a">안녕하세요</div>
    );
}

// 호출
const app = App();
console.log("element 생성", app)

터미널을 확인하면 아래와 같은 결과가 나옵니다.

App 함수 컴포넌트를 실행시키면 return 값은 jsx가 됩니다. 앞서 jsx을 바벨이 브라우저가 이해할 수 있는 자바스크립트로 변환하면 createElement 함수를 호출한다고 했습니다. element를 반환하는 createElement 함수를 우리가 정의했기 때문에 최종적으로는 element가 반환되게 됩니다.

결론적으로 앞서 설명한 흐름을 따르게 됩니다.

정리하면 App() 함수를 호출하면 return 값이 jsx 이므로 createElement를 자동으로 호출하여 element를 반환합니다.

이제 우리는 element 객체를 직접 생성할 필요가 없이 jsx로 컴포넌트만 생성하면 element를 얻을 수 있게 되었습니다.

최종적으로 createElement 함수는 다음과 같은 모습을 가집니다. 여기서 if문이 있는 부분은 형태로 함수를 렌더링할 때 사용됩니다.

function createElement(type, props, ...children) {

        // <App />으로 호출된 경우, type이 App 함수가 되고 이를 호출하여 element를 생성합니다.
    if (typeof type === "function") {
        return type(null, props, ...children);
    }

    return {
        type,
        props: {
            ...props, 
            children
        }
    }
}

3. render 함수 호출

이제 render 함수를 호출해보겠습니다.

render(<App/>, document.getElementById("root"))

이 코드는 리액트에서 html dom에 렌더링하는 코드와 동일합니다.

4. render 함수 정의

이제 함수를 rendering 해보겠습니다

function render(element, container) {
    const dom = createDom(element)
    container.appendChild(dom)
}
  1. element
    • element는 함수형 컴포넌트인 <App />입니다.
    • <App />으로 정의하는 순간 createElement이 호출됩니다.
    • App()과 다르게 아직 호출되지 않았기 때문에 정의된 함수로 표현됩니다.
    • createElement에서 if문에서 App 함수를 호출하여 처리합니다.
    • 결론적으로 element는 리액트에서 정의하는 element입니다.
    • 해당 element로 이전에 했던 작업처럼 dom에 렌더링하면 끝입니다.
  2. createDom
    • createDom은 element로 dom을 생성합니다.
function createDom(element) {

    if(typeof element === "string") {
        return document.createTextNode(element)
    }

    const dom = document.createElement(element.type);

    element.props.children
        .map(createDom)
        .forEach(child =>
            dom.appendChild(child))

    return dom;
}

이제 브라우저를 확인해보면 "안녕하세요"가 잘 호출됨을 알 수 있습니다.

정리

jsx는 element를 편하게 생성하기 위해 사용됩니다.

바벨이 jsx를 브라우저가 이해하는 자바스크립트로 변환시키면 createElement라는 함수를 호출합니다. 개발자는 createElement 함수를 정의하여 element를 반환하기면 하면 됩니다.

이제 리액트 프레임워크와 비슷한 모양을 가지게 되었습니다. 이제 hook을 통하여 컴포넌트의 상태를 변경해보도록 하겠습니다.

관련 컨텐츠

  1. 자바스크립트로 만들어 보는 리액트 프레임워크 - 1. 기본 컨셉
  2. 자바스크립트로 만들어 보는 리액트 프레임워크 - 2. jsx
  3. 자바스크립트로 만들어 보는 리액트 프레임워크 - 3. hook
  4. 자바스크립트로 만들어 보는 리액트 프레임워크 - 4. redux
  5. 자바스크립트로 만들어 보는 리액트 프레임워크 - 5. thunk
728x90