React 렌더링

하드 코딩된 HTML 마크업

1
2
3
4
5
6
7
8
9
10
11
<!-- Actual DOM Node : Container -->
<div class="app" lang="en">
<h1 class="app__headline">
React
<abbr title="Application Programming Interface">API</abbr>
</h1>
<ul class="app__list list--api">
<li>React</li>
<li>ReactDOM</li>
</ul>
</div>

DOM API - Actual DOM Tree 로 변경

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!-- Programming DOM Script : Actual DOM Tree-->
<div id="root"></div>

<script>
const rootNode = document.getElementById('root');
{
const abbrNode = document.createElement('abbr');
abbrNode.setAttribute('title', 'Application Programming Interface');

const abbrContent = document.createTextNode('API');
abbrNode.appendChild(abbrContent);

const headingNode = document.createElement('h1');
headingNode.insertAdjacentText('beforeend', 'React');
headingNode.insertAdjacentText('beforeend', ' ');
headingNode.insertAdjacentElement('beforeend', abbrNode);

const appNode = document.createElement('div');
appNode.classList.add('app');
appNode.setAttribute('lang', 'en');
appNode.insertAdjacentElement('beforeend', headingNode);

console.dir(appNode);

rootNode.appendChild(appNode);
}
</script>
  • console.dir(appNode); → 실제 DOM 노드

React API - Virtual DOM Tree 로 변경

CDN : React Library → React.createElement(), React.forwardRef() 등…

React 앱을 구성하는 가상 DOM을 생성하는 API

1
<script src="//unpkg.com/react/umd/react.development.js" crossorigin></script>

React.create.Element()

1
2
3
4
5
React.createElement(
type,
[props],
[...children]
)
1
2
3
4
5
6
const appElement = React.createElement(
'div',
{ className: 'app', lang: 'en' },
headingElement,
listElement
);

인자로 주어지는 타입에 따라 새로운 React Element를 생성하여 반환한다.

  • type 인자로는 HTML Standards Component를 문자열로 입력한다. 혹은 React 컴포넌트 타입이나 React Fragment 타입 중 하나가 올 수도 있다.
  • props는 반드시 object(객체) 타입이어야 한다.
  • children은 null 또는 undefined 또는 React.ReactNode 타입이다.

React 최상위 API - React

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// React element 생성
const headingElement = React.createElement(
'h1',
{ className: 'app__headline' },
'React',
' ',
React.createElement('abbr', { title: 'Application Programming Interface', children: 'API' })
);

const listElement = React.createElement('ul', {
className: 'app__list list--api',
children: [
React.createElement('li', null, 'React'),
React.createElement('li', null, 'ReactDOM')
]
});

const appElement = React.createElement(
'div',
{ className: 'app', lang: 'en' },
headingElement,
listElement
);

console.log(appElement)
  • console.log(appElement) → **가상 DOM 노드 = 그냥 “객체”**이다.

    • {$$typeof: Symbol(react.element), type: “div”, key: null, ref: null, props: {…}, …}

      $$typeof: Symbol(react.element)
      props: {className: “app”, lang: “en”, children: Array(2)}
      children: Array(2)
      0: {$$typeof: Symbol(react.element), type: “h1”, key: null, ref: null, props: {…}, …}
      1: {$$typeof: Symbol(react.element), type: “ul”, key: null, ref: null, props: {…}, …}
      length: 2
      className: “app”
      lang: “en”
      type: “div”

실제 DOM 노드 VS 가상 DOM 노드

실제 DOM 노드는 아주 많은 속성들이 있었는데 react의 가상 DOM 노드는 심플한 객체이다.

= 불필요한 건 빼고 필요한 것들로만 객체를 만들었다. = 추상화를 했다.

= 그래서 더 빠르나 실제 DOM이 아니기 때문에 변환과정(= render()함수를 사용)이 필요하다.

실제 DOM 으로 변환

= 생성한 가상 DOM 노드를 렌더링하기 위해 아래의 API를 추가하여 render()함수를 사용한다.

CDN: ReactDOM Library → ReactDOM.render()

React에 의해 구성된 앱(가상 DOM)을 실제 DOM에 렌더링 되도록하는 API

(비교 | CDN: React Library - React 앱을 구성하는 가상 DOM을 생성하는 API)

1
<script src="//unpkg.com/react-dom/umd/react-dom.development.js" crossorigin></script>

React 최상위 API - React

ReactDOM.render()

가상 DOM Tree(React 루트 요소)를 실제 DOM에 마운트(Mount)하여 렌더링(Rendering)한다.

1
ReactDOM.render(element, container[, callback])
1
2
3
// vNode → 가상 DOM Tree (React 요소로 구성)
// domNode → 실제(actual) DOM 노드
ReactDOM.render(vNode, domNode);
1
render(appElement, rootNode);

ReactDOM - React

우클릭 > 페이지 소스 보기

페이지 소스 보기에서는 < div id=”root” >< / > 안에 아무것도 없다.

= 즉, 검색봇이 크롤링을 할 때 좋지 않다.

= 검색 엔진 최적화X

Babel Standlone.

ES6+ 문법의 경우, ES6+ 문법을 지원하지 않는 브라우저에서는 깨질 수 있다.

그래서 node.js에서 Babel을 사용해서 버전을 내려주었다.

Babel Standalone 을 사용하면, 웹 브라우저 환경에서 버전을 내려줄 수 있다.

Babel 컴파일러가 실시간 컴파일 하도록 Bable의 독립형 빌드를 제공하기 때문.

1
<script src="https://unpkg.com/babel-standalone/babel.min.js" crossorigin></script>

@babel/standalone · Babel

위 코드를 불러오기만 한다고 컴파일되지는 않는다.

아래와 같이 < script >태그에 bind type을 지정해서 컴파일할 코드를 지정해줘야한다.

왜? 효율적으로 관리하기 위해서 바꿔주고자 하는 코드가 어떤 건지 식별할 수 있어야하기 때문.

bind type으로 text/babel 또는 text/jsx 타입이 적용하면,

브라우저에 로드될 때, Bablel Standalone이 해당 스크립트 태그들 모두를 컴파일 하고 실행한다.

(표준에서 bind type은 text/ecmascript 나 text/javasctipt )

1
<script type="text/babel"></>

브라우저에서 결과를 확인하면 < head > 영역에 없던 < script > 태그가 생겨있다.

< script >태그 안에서 Babel이 컴파일한 결과 코드를 볼 수 있다.

ES6+의 문법이 그 아래 버전으로 변환되어 있는 것을 확인할 수 있다.

변환은 브라우저가 렌더링될 때 실시간으로 컴파일된다.

! 브라우저에서 Babel을 사용하면 아래와 같은 경고가 출력된다.

서비스를 생산할 때 Babel을 이용할 경우, 일반적으로 Babel Standalone은 권장되지 않기 때문.

왜? 느리기 때문.

대신 Webpack 같은 Node.js에서 실행되는 빌드 시스템을 사용해 JavaScript를 사전에 미리 컴파일 해야한다.

그럼 언제 사용해야하나? (바벨 공식문서 - When (not) to use @babel/standalone 에 나와있다.)

브라우저에서 Babel 사용 시 경고( = babel standalone 사용 시)

CDN: ReactDOMServer Library → ReactDOMServer.renderToString(), renderToStaticMarkup() 등…

실제 DOM에 렌더링 하는 대신, 서버에서 React 앱을 렌더링 하기 위한 API

(클라이언트에서는 쓸 일이 없고 express 서버 등 서버쪽에서 렌더링을 할 때 필요하다. )

1
<script src="//unpkg.com/react-dom/umd/react-dom-server.browser.development.js" crossorigin></>

ReactDOMServer - React

ReactDOMServer.renderToStaticMarkup()

서버에서 렌더링할 때 사용하므로, 서버에서 렌더링 된 결과를 클라이언트로 보내기 위해

React 객체를 HTML String 값으로 변환한다.

= 문자로 만드는 것이 목표

?????renderToString()과 비슷하지만 내부적으로 React의 고유한 key 등을 포함하고 있어 순수한 HTML String값으로 반환된다.?????

1
2
3
// ReactDOMServer API
const { renderToStaticMarkup } = ReactDOMServer;
console.log(renderToStaticMarkup(appElement));

appElement를 renderToStaticMarkup() 메서드를 이용해 텍스트로 변환하면 아래와 같은 HTML String 값이 반환된다.

= 가상 노드가 실제 노드로 변경된 후 텍스트로 변환된 것

= HTML String 값

1
2
3
4
5
6
7
8
9
10
<div class="app" lang="en">
<h1 class="app__headline">
React
<abbr title="Application Programming Interface">API</abbr>
</h1>
<ul class="app__list list--api">
<li>React</li>
<li>ReactDOM</li>
</ul>
</div>

JSX

JSX는 ECMAScript에서 사용할 수 있는 XML과 유사한 구문 확장

ECMAScript 표준에 통합하지는 제안은 아니다.

= 그런 측면에서 React에서 사용한다.

= ES6+에서 기본 표현식(Primary Expression)을 확장한 자바스크립트의 식이다.

= ES6+ 미만에서는 babel이 필요(지금은 babel standalone 사용하니 괜찮다.)

= < Dropdown >, < Menu >, < MenuItem > 등 개발자가 만든 컴포넌트를 이용한다.

xml 유형의 문법이라서 self closing을 꼭 해야한다. 안 하면 오류이다. 그런데 React가 알려준다.

엔진이나 브라우저에서 구현하기 위한 것이 아니다.

(JSX, Jest, React 다 facebook에서 만들었다.)

JSX is an XML-like syntax extension to ECMAScript without any defined semantics. It’s NOT intended to be implemented by engines or browsers. It’s NOT a proposal to incorporate JSX into the ECMAScript spec itself. It’s intended to be used by various preprocessors (transpilers) to transform these tokens into standard ECMAScript.

JSX | XML-like syntax extension to ECMAScript

1
2
3
4
5
6
7
8
9
10
11
12
// Using JSX to express UI components.
var dropdown =
<Dropdown>
A dropdown list
<Menu>
<MenuItem>Do Something</MenuItem>
<MenuItem>Do Something Fun!</MenuItem>
<MenuItem>Do Something Else</MenuItem>
</Menu>
</Dropdown>;

render(dropdown);

JSX 로 변경

1
2
3
4
5
6
7
8
9
10
11
render(
<div className='app' lang='en' /* 여기에도 주석 삽입 가능 */>
{headingElement}
{listElement}
{/* 괄호 안은 JS로 동작하므로 이렇게 주석을 달아야 한다. */}
</div>,
rootNode,
() => {
console.log('React VDOM Tree with JSX-> render -> Actual DOM Mount');
}
);
  • < div ~ >~< / > 는 자바스크립트의 식이지 HTML template이 아니다.
    = class가 아니라 className을 써야한다.

  • headingElement와 listElement가 {} 보간에 의해 app JSX에 끼워넣어진 것이다.

  • {} 는 React에서 JSX를 사용할 때, 다른 컨텐츠를 끼워넣기 위해 사용하는 보간(Interpolation)

    여기서 {} 안은 JS로 동작한다.

    (마치 JavaScript에서 ``(백틱)을 사용할 때, 다른 컨텐츠를 끼워넣기 위한 보간인 ${}과 같다.)

  • <></>와 같이 fragment로 감싸줄 수 있는 데 이는 뒤에서 자세히 설명한다.

  • JSX에서 {false}, {null}, {undefined} 등 0을 제외한 falsy한 값은 렌더링 시 아무 것도 나타나지 않는다.

  • JSX에서 &&(엔드 연산자)와 삼항 연산자를 이용해 조건부 렌더링이 가능하다.

    • { isTrue && < div >< /> }
    • < div >{ isTrue ? ‘true다’ : ‘false다.’ }< />

babel이 JSX인 위 코드를 아래와 같이 React API로 변경해준다.

1
2
3
4
5
6
7
render(
React.createElement('div', { className: 'app', lang: 'en' }, headingElement, listElement),
rootNode,
function () {
console.log('React VDOM Tree with JSX-> render -> Actual DOM Mount');
}
);

JSX : React VDOM Tree -> render -> Actual DOM Mount

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function App(props /* { frameworkName, style } */) {
return (
<div classname='app' lang='en' style={props.style}>
<h1 className='app__headline'>
React <abbr title='Application Programming Interface'>API</abbr>
</h1>
<ul className='app__list list--api'>
<li>React</li>
<li>ReactDOM</li>
</ul>
</div>
);
}
// React VDOM Tree → render → Actual DOM Mount
render(
// React.createElement(App, { frameworkName: 'React', count: 12 }),
<App
frameworkName='React'
style={{ border: '6px solid currentColor', padding: '2rem 4rem' }}
/>,
rootNode
);