이전 글: https://moneytech.kr/54
앞에서 react.js 파일에 createElement라는 함수를 만들어서 app.js의 코드량을 조금은 줄였다.
여기서 React 개발진들은 함수 호출보다 더 나은 방식을 생각해내는데,
바로 '마크업(Markup)' 방식으로 React를 이용하는 것이다.
형태는 HTML처럼 Tagging 하듯이 태그로 감싸서 코딩하고,
그것을 입력값으로 받으면 내부적으로는 함수를 호출한 것처럼 작동하는 것이다. (createElement)
이것이 우리가 알고 있는 JSX라는 문법이다.
우리가 알고 있는 JSX형태로 바꿔보자.
import { createDOM, createElement, render } from './react';
const vdom = createElement('p', {},
createElement('h1', {}, "React 만들기"),
createElement('ul', {},
createElement('li', { style: "color:red" }, "첫 번째 아이템"),
createElement('li', { style: "color:blue" }, "두 번째 아이템"),
createElement('li', { style: "color:green" }, "세 번째 아이템"),
)
);
const vdom2 = <p>
<h1>React 만들기</h1>
<ul>
<li style="color:red">첫 번째 아이템</li>
<li style="color:blue">두 번째 아이템</li>
<li style="color:green">세 번째 아이템</li>
</ul>
</p>
console.log(vdom2);
render(vdom2, document.querySelector('#root'));
이렇게 하면 오류가 발생하는데, 그 이유는 Babel에서 트랜스파일할 때 JSX를 자동으로
React가 가지고 있는 함수로 변환해주는데 우리는 페이스북의 React를 import하지 않았다.
이것을 실제 Babel에서는 아래와 같이 해석한다.
이것을 우리가 만든 함수로 대체할 수 있을까?
다행히도, Babel의 가장 위에 주석으로 옵션을 넣으면 함수명을 바꿀 수 있다.
현재는 React.createElement를 호출하지만, 우리가 만든 createElement를 호출하게 해 보자.
파일 가장 위에 아래와 같이 주석을 삽입한다.
/* @jsx createElement */
실제 이렇게 삽입한 결과를 Babel창으로 보면?
기존 React에 있던 함수를 호출하는 것에서 우리가 만든 함수명으로 바뀐 것을 알 수 있다.
이제 app.js를 아래와 같이 변경한다.
/* @jsx createElement */
import { createDOM, createElement, render } from './react';
const vdom = <p>
<h1>React 만들기</h1>
<ul>
<li style="color:red">첫 번째 아이템</li>
<li style="color:blue">두 번째 아이템</li>
<li style="color:green">세 번째 아이템</li>
</ul>
</p>
console.log(vdom);
render(vdom, document.querySelector('#root'));
이렇게 바꿔도 제대로 실행되지 않는다.
props가 빈 객체로 넘겨졌을 때 Babel은 그것을 null로 바꿔버린다.
(※ 중요: 우리가 이전에 react에서 만든 것은, 축약형으로 적어줬는데 그 의미는 중간의 props가 키 값 자체가 null이 된다는 것이다. key값을 null로 처리하려고 하니까 될 리가 없다.)
그래서 우리가 만든 함수에서도 null처리를 해줘야 한다.
// react.js
export function createDOM(node) {
if (typeof node === 'string') {
return document.createTextNode(node);
}
const element = document.createElement(node.tag);
Object.entries(node.props)
.forEach(([name, value]) => element.setAttribute(name, value));
node.children
.map(createDOM)
.forEach(element.appendChild.bind(element));
return element;
}
export function createElement(tag, props, ...children) {
props = props || {};
return { tag, props, children };
}
export function render(vdom, container) {
container.appendChild(createDOM(vdom));
}
여기서의 팁은, 디폴트 파라미터로 바꿔주면 안 된다는 것이다.
export function createElement(tag, props = {}, ...children) {
return { tag, props, children };
}
왜냐하면 디폴트 파라미터는 해당 값이 undefined로 들어올 때만 작동되는데,
null의 경우 Object로 취급해서 작동하지 않는다.
(번거롭지만 자바스크립트를 있는 그대로 사랑하도록 노력하자...)
이제 위에 있는 react.js처럼 바뀌었을 때 정상적으로 작동하는 것을 확인할 수 있다.
이런 식으로 페이스북에서는 JSX라는 마크업 언어를 통해 React를 편하고 쉽게 만들었다.
이 과정을 통해서 구조도 잘 파악이 되고 작성도 쉽게 되었다.
모든 것을 처음부터 만들기 위해서는 Babel.js와 같은 변환기도 만들어야겠지만
우리.. 꼭 그렇게까지 할 필요는 없잖아...
< 참고할만한 글 >
1. Babel이 JSX를 어떻게 바꾸는가
https://babeljs.io/docs/en/babel-plugin-transform-react-jsx
2. React에서 설명하는 JSX 문법
https://ko.reactjs.org/docs/introducing-jsx.html