처음 만난 리액트 문서


4.1 Element에 대해 알아보기

4.1.1 Element의 정의

Element라는 영어 단어는 요소, 성분이라는 뜻을 갖고 있습니다. 어떤 물체를 구성하는 성분을 영어로 element라고 부릅니다. 마찬가지로 리액트의 element도 리액트 앱을 구성하는 요소를 의미합니다. 리액트 공식 홈페이지에서는 element를 아래와 같이 정의하고 있습니다.

Elements are the smallest building blocks of React apps.

위 문장을 한글로 번역해보면 Element는 리액트 앱의 가장 작은 빌딩 블록들이라는 의미가 됩니다. 즉, 리액트 앱을 구성하는 가장 작은 블록들을 element라고 부르는 것이죠. 이전에 웹 개발을 했던 분들은 이미 element라는 용어를 많이 들어봤을 겁니다. element는 원래 웹사이트에 대한 모든 정보를 담고있는 객체인 DOM(Document Object Model)에서 사용하는 용어입니다. 그래서 기존에 element라고 하면 DOM Element를 의미했습니다. DOM Element가 어떻게 생겼는지 한 번 볼까요? 아래 그림은 우리가 앞에서 만든 리액트 애플리케이션을 실행한 뒤에, 크롬 브라우저의 개발자 도구에서 'Elements' 탭을 누른 모습입니다.

크롬 개발자 도구의 element탭

위 그림에서 보이는 것처럼 탭 이름부터 Elements로 되어 있기 때문에, element들을 모아놓은 것이라는 것을 알 수 있습니다. 하지만 여기에 나타난 element들은 리액트 element가 아니라 DOM Element이며, HTML 요소를 나타냅니다. 실제로 우리가 화면에서 볼 수 있는 것들이죠. 그렇다면 리액트 elementDOM Element는 어떤 차이가 있을까요?

리액트가 개발되기 시작한 아주 초창기에, 화면에 나타나는 내용들을 기술하는 자바스크립트 객체를 일컫는 용어가 필요했습니다. 그래서 처음에는 '기술한다'는 그 의미 그대로 descriptor라는 이름으로 불렀습니다. 하지만 descriptor가 결국 최종적으로 나타나는 형태는 DOM Element였기 때문에, DOM과의 통일성을 위해서 element라고 부르기로 결정하였습니다. 아래 그림은 리액트 elementDOM Element를 나타낸 것입니다.

리액트 elements와 DOM elements

앞 장에서 우리가 리액트의 Virtual DOM에 대해서 배웠습니다. 실제 브라우저의 DOM에 존재하는 elementDOM Element가 되는 것이고, 리액트의 Virtual DOM에 존재하는 element가 바로 리액트 element가 되는 것입니다. 결국 리액트 elementDOM Element의 가상 표현이라고 볼 수 있습니다. 그리고 DOM Element는 리액트 element에 비해서 많은 정보를 담고 있기 때문에 상대적으로 크고 무겁습니다. 앞으로 이 문서에서 말하는 element는 특별한 언급이 없는 한 리액트 element를 의미한다고 보면 됩니다.

리액트 element는 화면에서 보이는 것들을 기술합니다. element가 기술한 내용을 토대로 실제 우리가 화면에서 보게되는 DOM Element가 만들어지는 것입니다. 앞 장에서 JSX를 배울 때 살펴봤던 예제 코드를 다시 한 번 보도록 하겠습니다.

const element = <h1>Hello, world</h1>;

이 코드는 JSX를 사용하여 작성된 코드입니다. 앞에서는 element에 대해 배우지 않았기 때문에 그냥 지나쳤지만, 대입 연산자('=') 왼쪽 부분에 나오는 변수의 이름이 element로 되어 있는 것을 볼 수 있습니다. 이 코드가 실행될 때, 대입 연산자의 오른쪽 부분은 리액트의 createElement()함수를 사용하여 element를 생성하게 됩니다. 결국 이렇게 생성된 것이 바로 리액트 element가 되는 것입니다. 그리고 리액트는 이 element를 이용해서 실제 우리가 화면에서 보게 될 DOM Element를 생성합니다.

4.1.2 Element의 생김새

그렇다면 리액트 element는 실제로 어떻게 생겼을까요? element가 화면에 보이는 것들을 기술한다고 했는데, 실제로 element는 어떤 형태로 존재하는지 의문이 들 수 있습니다. 결론부터 말하면, 리액트 element는 자바스크립트 객체 형태로 존재합니다. element는 컴포넌트 유형(예: Button)과 속성(예: color) 및 내부의 모든 자식(children)에 대한 정보를 포함하고 있는 일반적인 자바스크립트 객체입니다. 그리고 뒤에서 나오겠지만 이 객체는 맘대로 변경할 수 없는 불변성(immutability) 을 갖고 있습니다. 한 번 생성되면 바꿀 수 없다는 뜻이죠. 그럼 element의 실제 모습을 한 번 보도록 할까요?

{
    type: 'button',
    props: {
        className: 'bg-green',
        children: {
            type: 'b',
            props: {
                children: 'Hello, element!'
            }
        }
    }
}

위 코드는 버튼을 나타내기 위한 element입니다. 단순한 자바스크립트 객체임을 알 수 있죠? 위 코드처럼 type에 HTML 태그 이름이 문자열로 들어가는 경우에 element는 해당 태그 이름을 가진 DOM Node를 나타내고 props는 속성에 해당합니다. props에 대해서는 뒤에서 자세히 배울 예정이니 지금은 그냥 넘어가도 됩니다. 그리고 위 element가 실제로 렌더링이 된다면 아래와 같은 DOM Element가 될 것입니다.

<button class='bg-green'>
    <b>
        Hello, element!
    </b>
</button>

그렇다면 elementtype에 HTML 태그 이름이 문자열로 들어가는 것이 아닌 경우에는 어떻게 될까요? 아래 자바스크립트 코드는 리액트의 Component Element를 나타낸 것입니다. 이 역시도 일반적인 자바스크립트 객체입니다. 다만, 위에 나왔던 element와 한 가지 다른 점은 type에 HTML태그가 아닌 리액트 컴포넌트의 이름이 들어갔다는 점입니다.

{
    type: Button,
    props: {
        color: 'green',
        children: 'Hello, element!'
    }
}

이처럼 리액트 element는 자바스크립트 객체 형태로 존재합니다. 그리고 이 객체를 만드는 역할을 하는 것이 바로 앞에서 나왔던 createElement()함수입니다. 앞에서 createElement()함수를 호출 할 때 세 가지의 파라미터를 넣었었는데, 그 부분을 다시 한 번 보도록 할까요?

React.createElement(
    type,
    [props],
    [...children]
)

첫 번째 파라미터로는 타입이 들어갑니다. 여기에는 HTML태그 이름이 문자열로 들어가거나, 또 다른 리액트 컴포넌트가 들어가게 됩니다. 이것이 결국 아까 우리가 개발자 도구를 통해서 보았던 HTML태그가 되는 것이죠. 만약 여기에 HTML태그가 아닌 리액트 컴포넌트를 넣으면 어떻게 될까요? 모든 리액트 컴포넌트는 최종적으로는 HTML태그를 사용하게 되어있습니다. 하나의 컴포넌트는 여러개의 자식 컴포넌트들을 포함할 수 있고, 자식 컴포넌트들을 모두 쭉 분해해보면 결국 HTML태그가 나오는 것이죠.

두 번째 파라미터로는 props라는 것이 들어갔었습니다. 우리가 아직 props에 대해서 배우지 않았기 때문에, 이 부분은 그냥 간단하게 element의 속성이라고 설명해보도록 하겠습니다. 아까 개발자 도구의 그림에서 HTML태그가 있고, 해당 태그에 여러가지 속성들이 들어가 있었죠? 예를 들면, className이나 style같은 것들 말이죠. 이런 속성을 attributes라고 부릅니다. 그리고 propsattributes보다는 조금 더 상위에 있는 복잡한 개념이지만 지금은 일단 element의 속성이라고만 이해하고 넘어가도록 하겠습니다.

그리고 세 번째 파라미터로는 children이 들어가게 됩니다. 해당 element의 자식 element들이 이 부분에 들어가게 됩니다. 실제 개발자 도구의 그림에서는 하나의 HTML태그 하위에 또 여러개의 HTML태그가 나오는 것을 볼 수 있었습니다. 이러한 HTML태그들이 결국 자식 element가 되는 것입니다.

이제 실제로 createElement()함수가 동작하는 과정을 코드와 함께 살펴보도록 하겠습니다. 아래 예시 코드를 한 번 보겠습니다.

function Button(props) {
    return (
        <button className={`bg-${props.color}`}>
            <b>
                {props.children}
            </b>
        </button>
    )
}

function ConfirmDialog(props) {
    return (
        <div>
            <p>내용을 확인하셨으면 확인 버튼을 눌러주세요.</p>
            <Button color='green'>확인</Button>
        </div>
    )
}

위 코드에는 Button컴포넌트와 ConfirmDialog컴포넌트가 있으며, ConfirmDialog컴포넌트가 Button컴포넌트를 포함하고 있습니다. 여기서 ConfirmDialog컴포넌트의 element는 어떤 모습이 될까요? 아마도 아래와 같은 형태가 될 것입니다.

{
    type: 'div',
    props: {
        children: [
            {
                type: 'p',
                props: {
                    children: '내용을 확인하셨으면 확인 버튼을 눌러주세요.'
                }
            },
            {
                type: Button,
                props: {
                    color: 'green',
                    children: '확인'
                }
            }
        ]
    }
}

여기서 첫 번째 childrentype이 HTML태그인 p태그이기 때문에 곧바로 렌더링이 될 수 있는 상태입니다. 하지만 두 번째 childrentype은 HTML태그가 아니라 리액트 컴포넌트 이름인 Button입니다. 이 경우에 리액트는 Button컴포넌트의 element를 생성해서 합치게 됩니다. 그래서 최종적으로 element는 아래와 같은 모습이 될 것입니다.

{
    type: 'div',
    props: {
        children: [
            {
                type: 'p',
                props: {
                    children: '내용을 확인하셨으면 확인 버튼을 눌러주세요.'
                }
            },
            {
                type: 'button',
                props: {
                    className: 'bg-green',
                    children: {
                        type: 'b',
                        props: {
                            children: '확인'
                        }
                    }
                }
            }
        ]
    }
}

이처럼 컴포넌트 렌더링을 위해서 모든 컴포넌트가 createElement()함수를 통해 element로 변환된다는 것을 기억하시기 바랍니다. 지금까지 리액트의 element는 실제로 어떻게 생겼는지 알아보았습니다. 리액트의 element는 우리 눈에 실제로 보이는 것들을 기술한다는 사실을 기억하면서 다음으로 넘어가도록 하겠습니다.

4.1.3 Element의 특징

리액트의 element는 굉장히 중요한 특징을 갖고 있습니다. 바로 불변성(immutability) 입니다. 불변성은 말 그대로 변하지 않는 성질을 의미합니다. 즉, element가 불변성을 갖고 있다는 것은 한 번 생성된 element는 변하지 않는다는 것입니다. 다른 말로 표현하면, element 생성 후에는 children이나 attributes를 바꿀 수 없다는 말입니다.

그렇다면 여기서 의문이 하나 생기게 됩니다. 리액트의 element라는 것은 우리 눈에 보이는 것을 기술한다고 했는데, 'element가 변할 수 없다면 화면 갱신이 안되는 것 아닌가?' 라는 의문을 가질 수 있습니다. 이러한 의문을 해소하려면 element의 불변성에 관한 설명을 다시 한 번 자세히 읽어보아야 합니다.

element 생성 후에는 children이나 attributes를 바꿀 수 없다

여기서 우리가 빠트린 부분이 있죠. 바로 element 생성 후에는이라는 부분입니다. 즉, element는 다양한 모습으로 존재할 수 있지만, 한 번 생성된 다음에는 변경이 불가능하다는 뜻입니다. 우리가 붕어빵 가게를 가보면 붕어빵 틀에 반죽을 넣고 시간이 지나면 붕어빵이 구워져서 나오는데, 한 번 구워져서 나온 붕어빵의 속 내용을 바꿀 수 없는 것과 같은 이치라고 생각하면 됩니다. 아래 그림처럼 말이죠.

Component와 element

위 그림에는 리액트의 컴포넌트와 element의 관계가 나타나 있습니다. 뒷 장에서 컴포넌트를 배울 때 자세히 다루겠지만 컴포넌트는 일종의 붕어빵 틀이라고 보면 됩니다. 그리고 붕어빵이 구워져서 밖으로 나오는 과정이 element를 생성하는 과정이고, 붕어빵이 모두 완성되면 element가 생성이 끝난 것이기 때문에 변경할 수 없는 것이죠.

그렇다면 화면에 변경된 element들을 보여주기 위해서는 어떻게 해야할까요? 그런 경우에는 기존 element를 변경하는 것이 아니라, 새로운 element를 만들면 됩니다. 새로운 element를 만들어서 기존 element와 바꿔치기 하는 것이죠. 우리가 앞에서 리액트의 장점중 하나로 빠른 렌더링 속도가 있다는 것을 배웠습니다. 그리고 그걸 달성하기 위해서 내부적으로 Virtual DOM이라는 것을 사용한다고 했었죠. Virtual DOM의 개념도를 다시 한 번 살펴보도록 하겠습니다.

Virtual DOM 개념도

위 그림에서 보이는 것처럼 화면에 새로운 내용을 보여주기 위해서 Virtual DOM은 변경된 부분을 계산(Compute Diff)하고, 해당 부분만을 다시 렌더링 합니다. 여기서 동그란 각 원들이 바로 element입니다. 그리고 빨간색으로 표시된 원들은 변경된 element들이 되는 것이죠. element는 불변성을 갖고 있기 때문에, 화면에 새로운 내용을 보여주기 위해서는 새로운 element를 만들어서 기존 element가 연결되어 있는 부분에 바꿔서 달면 됩니다.

지금 배운 리액트 element의 불변성이라는 특징을 잘 기억해두시기 바랍니다. 실제로 리액트를 사용해서 개발하다 보면 상태 관리와 더불어 화면이 얼마나 자주 갱신되는지가 성능에 큰 영향을 미치는데, 그 과정이 모두 element가 새롭게 생성된다는 것을 이해하고 있으면 좀 더 원리를 잘 이해하고 효율적으로 개발할 수 있습니다.


마지막 업데이트: 2025년 08월 21일 07시 59분

이 문서의 저작권은 이인제(소플)에 있습니다. 무단 전재와 무단 복제를 금합니다.

On this page