react 기본 익히기 - element에 대하여-

1. element의 정의

 

리액트 앱을 구성하는 요소

 

Elements are the smallest building blocks of React apps

 

"엘리먼트는 리액트 앱의 가장 작은 빌딩 블록들"

 

 

개발자 도구에서 볼 수 있는 elements 탭의 elements는 DOM element이고, HTML의 요소이다.

 

실제로 화면에서 볼 수 있는 것들

 

그러면 react element는..?

 

리액트 초창기에는 "화면에 나타나는 내용을 기술한 자바스크립트 객체"를 나타냈다고 한다

 

 

실제 브라우저의 DOM에 존재하는 엘리먼트는 DOM 엘리먼트가 되고, 리액트의 Virtual DOM에 존재하는 엘리먼트가 리액트 엘리먼트가 된다

 

리액트 엘리먼트는 DOM 엘리먼트의 가상 표현이 된다

 

DOM 엘리먼트는 리액트 엘리먼트에 비해 많은 정보를 담고 있어서 상대적으로 크고 무겁다

 

리액트 엘리먼트는 화면에서 보이는 것을 기술한다

 

엘리먼트가 기술한 내용을 토대로 실제 화면에서 보게 되는 DOM 엘리먼트가 만들어진다

 

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

 

대입연산자 왼쪽 부분에 나오는 변수 이름이 element로 되어있는데,

 

이 코드가 실행될때 대입 연산자의 오른쪽 부분 <h1>Hello, world</h1>이 react의 createElement()함수를 사용해서 엘리먼트를 생성하게 된다.

 

이렇게 생성된 엘리먼트가 리액트 엘리먼트가 된다.

 

리액트는 이 엘리먼트를 이용해서 실제 화면에서 보게될 DOM 엘리먼트를 생성하게 된다.

 

 

2. element는 어떻게 생겼나

 

자바스크립트 객체 형태로 존재함

 

element는 컴포넌트 유형과 속성, 내부의 모든 자식에 대한 정보를 포함하는 일반적인 자바스크립트 객체

 

마음대로 변경할 수 없는 불변성(immutable)을 가진다.

 

즉, 한번 생성되면 바꿀 수 없다

 

 

2-1) HTML 태그 이름이 type에 문자열로 들어가는 경우

 

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

 

위 코드는 button을 나타내기 위한 element로, 단순한 자바스크립트 객체형태이다.

 

type에 HTML 태그 이름이 문자열로 들어가면, element는 해당 태그 이름을 가진 DOM Node를 나타내며, 

 

props는 속성을 나타낸다.

 

만약 위 엘리먼트가 실제로 렌더링이 된다면...

 

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

 

위와 같은 DOM element가 된다.

 

 

2-2) HTML태그가 아닌 리액트 컴포넌트 이름이 들어가는 경우

 

만약 type에 HTML 태그 이름이 아니라 다른 것이 들어간다면?

 

아래 코드는 type에 리액트 컴포넌트의 이름 Button이 들어간 형태이다.

 

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

 

여전히 자바스크립트 객체이다.

 

리액트 엘리먼트는 이처럼 자바스크립트 객체 형태로 존재하게 된다.

 

이러한 객체를 만드는 것이 createElement()함수이다.

 

 

3. createElement()함수 복습

 

 

 

첫번째 파라미터로 타입, 여기에는 HTML 태그 이름이 문자열로 들어가거나 다른 리액트 컴포넌트가 들어갈 수 있다

 

이것이 결국 개발자 도구로 보았던 HTML 태그가 된다.

 

리액트 컴포넌트를 넣더라도, 모든 리액트 컴포넌트는 최종적으로 HTML 태그를 사용하므로,

 

하나의 컴포넌트는 여러 개의 자식 컴포넌트를 포함할 수 있으며, 자식 컴포넌트를 모두 쭉 분해해보면 결국 HTML 태그가 나온다.

 

두번째 파라미터로 props라는 것이 들어가는데, 엘리먼트의 속성이다.

 

세번째 파라미터로 children이 들어간다.

 

해당 엘리먼트의 자식 엘리먼트들이 이곳에 들어간다.

 

실제로 HTML 요소를 보면, 하나의 HTML 태그 하위에 다시 여러개의 HTML 태그가 나온다.

 

이렇게 하위에 나오는 HTML 태그들이 자식 엘리먼트가 된다.

 

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의 형태는?

 

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

 

 

첫번째 children은 type이 HTML 태그인 p태그이므로, 곧바로 렌더링이 될 수 있습니다.

 

하지만 두번째 children은 type이 HTML 태그가 아니라 리액트 컴포넌트 이름인 Button입니다.

 

        <div>
            <p>내용을 확인하셨으면 확인 버튼을 눌러주세요.</p>
            <Button color='green'>확인</Button>
        </div>

 

이 경우 리액트는 Button 컴포넌트의 엘리먼트를 생성해서 합치게 된다.

 

그래서 최종적으로는,,,

 

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

 

이처럼 컴포넌트 렌더링을 위해 모든 컴포넌트가 createElement()함수를 통해 엘리먼트로 변환된다

 

리액트 엘리먼트는 우리 눈에 실제로 보이는 것을 기술한다

 

 

4. 엘리먼트의 특징, 불변성

 

리액트 엘리먼트는 아주 중요한 특징으로 불변성(immutable)을 갖는다는 것이다.

 

불변성은 말 그대로 변하지 않는 성질이다.

 

한번 생성된 element는 변하지 않는다.

 

즉, 엘리먼트를 생성하고나서는, children이나 attributes를 바꿀 수 없다는 말이다.

 

그러면 엘리먼트가 우리 눈에 보이는 것을 기술하는 것인데, 이게 변할 수 없으면 화면 갱신은 어떻게 하는 것일까?

 

"엘리먼트 생성 후에는 children이나 attributes를 바꿀 수 없다"

 

"엘리먼트 생성 후"라는 부분이 중요하다.

 

엘리먼트는 다양한 모습으로 존재할 수 있지만, 한번 생성된 다음에는 변경이 불가능하다는 뜻이다.

 

붕어빵 틀에 반죽을 넣고 시간이 지나 그 안에서 붕어빵이 구워져 나오면, 구워져 나온 붕어빵 속 내용은 바꿀수가 없는 것과 같다.

 

 

위 그림에는 리액트 컴포넌트와 엘리먼트의 관계가 나타나 있다.

 

컴포넌트는 일종의 붕어빵 틀이다.

 

붕어빵이 구워져서 밖으로 나오는 과정이 엘리먼트를 생성하는 것이고, 완성된 붕어빵은 엘리먼트 생성이 끝나서, 변경할 수 없는 것이다.

 

화면에 변경된 엘리먼트들을 보여주기 위해서는 어떻게 해야할까?

 

이런 경우에는 기존 엘리먼트를 변경하는 것이 아니라, 그냥 새로운 엘리먼트를 만들면 된다.

 

새로운 엘리먼트를 만들어서 기존 엘리먼트와 바꿔치기 하는 것이다.

 

리액트의 장점 중 하나가 빠른 렌더링 속도이고 이를 위해 Virtual DOM을 사용하게 된다.

 

 

위 그림처럼 화면에 새로운 내용을 보여주기 위해 Virtual DOM은 변경된 부분을 계산(compute-diff)하고, 해당 부분만을 다시 렌더링(re-render)한다.

 

동그란 각 원들이 엘리먼트이다.

 

빨간색으로 표시된 원들은 변경된 엘리먼트들이 된다.

 

엘리먼트는 불변성을 가지고 있으므로, 화면에 새로운 내용을 보여줄려면 새로운 엘리먼트를 만들어서,

 

기존 엘리먼트가 연결되어 있는 부분에 바꿔 달면 된다.

 

상태관리, 화면이 갱신되는 횟수(얼마나 화면이 자주 바뀌는지)는 실제 리액트 개발 과정에서 성능에 크게 영향을 미치는 요소

 

엘리먼트가 새롭게 생성되어 바꿔치기 한다는 것을 이해하고 있다면 좀 더 효율적인 개발이 가능..?

 

 

4. 엘리먼트 렌더링

 

엘리먼트를 생성하고, 실제 화면에 보여주기 위한 과정

 

<div id="root"></div>

 

이 HTML 코드는 root라는 id를 가진 <div>태그이며, 모든 리액트 앱에 필수적으로 들어가는 태그이다.

 

이 <div>태그 안에 리액트 엘리먼트들이 렌더링되며, 이것을 Root DOM node라고 부른다.

 

이 <div>태그 안에 있는 모든 것이 리액트 DOM에 의해 관리된다.

 

오직 리액트만으로 만들어진 모든 웹사이트들은 단 하나의 Root DOM node를 가진다.

 

반면 기존에 있던 웹사이트에 추가적으로 리액트를 연동하게 되면 여러 개의 분리된 수많은 Root DOM node를 가질 수도 있다.

 

 

위 root <div>에 리액트 엘리먼트를 렌더링할려면 다음과 같이 할 수 있다.

 

const element = <h1>안녕, 리액트!</h1>;
ReactDOM.render(element, document.getElementById('root'));

 

이 코드는 엘리먼트를 하나 생성하고, 생성된 엘리먼트를 root div에 렌더링하는 코드이다.

 

ReactDom의 render()함수를 사용하게 된다.

 

첫번째 파라미터인 element를 두번째 파라미터인 document.getElementById('root')에 렌더링하게 되는 것이다.

 

첫번째 파라미터는 리액트 엘리먼트이고, 두번째 파라미터는 HTML 파라미터이다.

 

HTML 파라미터는 브라우저의 DOM에 존재하고, 리액트 엘리먼트는 리액트의 Virtual DOM에 존재한다.

 

리액트의 엘리먼트가 렌더링되는 과정은 Virtual DOM에서 실제 DOM으로 이동하는 과정이다.

 

 

5. 렌더링된 엘리먼트 업데이트하기

 

엘리먼트는 불변성때문에 한번 생성되면 바꿀 수 없어서 엘리먼트를 업데이트하기 위해서는 다시 생성해야 한다.,

 

function tick() {
    const element = (
        <div>
            <h1>안녕, 리액트!</h1>
            <h2>현재 시간: {new Date().toLocaleTimeString()}</h2>
        </div>
    );

    ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick,1000);

 

위 코드는 tick()이라는 함수를 정의하고 있다

 

tick()함수는 현재 시간을 포함하고 있는 엘리먼트를 생성해서 root div에 렌더링하는 역할

 

new Date()가 현재시간을 만드나봐..

 

자바스크립트의 setInterval()함수로 tick()함수를 매초마다 호출한다.

 

실제 실행하면 매초 화면에 새로운 시간이 나오게 될 것

 

내부적으로는 tick()함수가 호출될 때마다 기존 엘리먼트를 변경하는 것이 아니라 새로운 엘리먼트를 생성해서 바꿔치기 하는 것이다.

 

위 그림처럼 변경되는 부분이 반짝이는데

 

새로운 엘리먼트가 매초 생성되면서 기존 엘리먼트와 교체되어 내용이 변경되고, 변경된 부분에 반짝이는 효과가 나타난다.

 

리액트 엘리먼트의 불변성 때문에 엘리먼트를 업데이트하기 위해서는 새로 만들어야 한다는 것을 기억해야한다.

 

 

6. 실습 따라해보기

 

$npx create-react-app my-app으로 react app 생성

 

src 폴더에 chapter_04 폴더 작성하고, Clock.jsx를 다음과 같이 작성

 

import React from 'react';

function Clock(props){

    return (
        <div>
            <h1>안녕, 리액트!</h1>
            <h2>현재 시간: {new Date().toLocaleTimeString()}</h2>
        </div>
    );

}

export default Clock;

 

실제 화면에 렌더링하기 위해 index.js를 수정

 

1초마다 렌더링시키고 싶다면 setInterval()함수를 사용

 

import Clock from './chapter_04/Clock';

const root = ReactDOM.createRoot(document.getElementById('root'));

setInterval(() => {
  root.render(
  <React.StrictMode>
    <Clock />
  </React.StrictMode>
)
}, 1000);

 

npm start로 실행시키면, 갱신되는 부분이 개발자도구에 깜빡거리는 것을 확인할 수 있다

 

TAGS.

Comments