김민태 프론트엔드 강의 - javascript로 React 만들기
React 없이, JSX를 직접 쓰고, 가상 DOM을 만들고,
그걸 진짜 DOM으로 만들어서 화면에 그리는 기능을 직접 구현한다.
구성 요소 역할
createElement | JSX를 가상 DOM 객체로 변환함 |
createDOM | 가상 DOM 객체를 실제 DOM 요소로 변환 |
render | 만들어진 DOM을 브라우저에 붙임 |
//JSX를 가상 DOM 객체로 변환함
export function createVNode(tag, props, ...children) {
return { tag, props, children };
}
//가상 DOM 객체를 실제 DOM 요소로 변환
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;
}
// 만들어진 DOM을 브라우저에 붙임
export function render(vdom, container) {
container.appendChild(createDOM(vdom));
}
const Title = <div><h1>Hello</h1></div>
render(<Title/>, document.querySelector('#root'));
1단계 - JSX로 코드 작성
/* @jsx createVNode */
const Title = () => <div><h1>Hello</h1></div>;
*@jsx 주석 지시어 를 통해 바벨로 하여금 JSX를 어떤 함수로 변환할지 설정함.
React.createElement 대신 우리가 만든 createVNode 함수를 써야 하니까,
Babel에게 createVNode 사용하라고 알려주기 위해 @jsx 주석 지시어 사용.
(React 16 이전에는 ) 바벨이 import React from "react"; 통해서
jsx를 const element = React.createElement("h1", null, "Hello"); 변환과정 거쳤음.
React 17 이후에는 import 안해도 자동으로 react 패키지의 jsx-runtime 모듈에 정의된 jsx() 함수"를 Babel이 자동으로 import해서
const el = _jsx("h1", { children: "Hello" });
React.createElement를 대체함
JSX 코드
import React from "react"; /* 17 이전에는 React.createElement가 동작해야하므로 import React 필요*/
const element = <div><h1>Hello</h1></div>;
바벨 변환결과
const element = createVNode(
"div",
null,
createVNode("h1", null, "Hello")
);
//JSX를 가상 DOM 객체로 변환함
export function createVNode(tag, props, ...children) {
return { tag, props, children };
}
- React에서 컴포넌트는 반드시 함수여야 하고, 대문자로 시작해야 한다.
이 경우 Babel이 내부적으로 대문자로 시작하는 JSX 태그를 일반 문자열이 아니라 JavaScript 변수(컴포넌트)로 취급하여 React.createElement를 호출한다.
- tag가 함수 컴포넌트인지 확인 (typeof tag === 'function').
-
- 함수 컴포넌트 라면 tag(props)를 호출하여 실행 결과를 반환
- children이 1개면 단일 값으로 전달, 2개 이상이면 배열로 전달.
- <Title>이름</Title>
- <Title><Item>1</Item><Item>2</Item></Title> 구분
//jsx 코드로부터 변환된 vdom
const element = {
tag: "div",
props: null,
children: [
{
tag: "h1",
props: null,
children: ["Hello"]
}
]
};
render(<Title/>, document.querySelector('#root'));
이것은 즉
render( VDOM, document.querySelector('#root')); 인데
render함수는 vDom과 container를 받아 vDom 을 Dom으로 만들어 돔에 붙이는 함수임.
// 만들어진 DOM을 브라우저에 붙임
export function render(vdom, container) {
container.appendChild(createDOM(vdom));
}
//가상 DOM 객체를 실제 DOM 요소로 변환
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));
.forEach(child => element.appendChild(child));
return element;
}
vdom.children
.map(createDOM) // 자식들을 DOM으로 변환 → [<p>, <span>]
.forEach(child => element.appendChild(child)); // <div>에 붙이기
핵심 요약
.map(createDOM) → VDOM 배열 → DOM 배열
.forEach(...) → 그 배열을 현재 요소에 붙이는 작업