September 13, 2020
3회차는 React 의 기본적인 내용과 간단하게 구현해보는 것에 중점을 둔 강의였다.
기술을 단순하게 사용만 해보는 것이 아니라 직접 구현을 해보면서 어떻게 동작 & 구현되어있는지를 보고, 기술을 습득해야한다. 단순한 사용자로만 남으면 10년 뒤에 사람이 달라진다라는 강사님의 명언으로 시작 된 3번째 강의 내용이다.
React.js 의 사상을 이해하는 데에 문제 없는 정도의 핵심적인 부분만 만들어본다. Real DOM API 의 추상도가 낮기 때문에 Real DOM 을 직접 조작하게 되면 복잡도가 높아진다는 것을 알 수 있다. 아래는 아주 간단한 리액트 만들기의 시작인 코드이다.
index.js
const list = [
{ title: "React에 대해 알아봅시다." },
{ title: "Redux에 대해 알아봅시다." },
{ title: "TypeScript에 대해 알아봅시다." }
];
const rootElement = document.getElementById("root");
위 코드로 화면을 그리는 방법은 아래 코드와 같다.
function app(items) {
rootElement.innerHTML = `
<ul>
${items.map(item => `<li>${item.title}</li>`).join("")}
</ul>
`;
}
app(list);
DOM에 직접 접근하여 Real DOM API 를 통해 작업하는 것은 규모가 커지면 복잡해지기 때문에 React 라이브러리는 DOM 을 Virtual DOM 으로 변환하여 Javascript 에서는 쉬운 상태를 유지하면서 작업할 수 있게 하였다.
React, Vue 같은 transpiling 이 되는 코드 같은 경우에는 컴파일 타임이라는 새로운 레이어가 생겼기 때문에 꼭 인지하고 있어야 한다.
/* @jsx createElement */
function renderElement(node) {
if (typeof node === "string") {
return document.createTextNode(node);
}
const el = document.createElement(node.type);
node.children.map(renderElement).forEach(element => {
el.appendChild(element);
});
return el;
}
/* @jsx createElement */
라인은 compile time에 babel이 transpiling 하여 React 대신 createElement 를 사용할 수 있다.
function render(virtualDom, container) {
// 여기서 virtual dom 과 새로운 virtual dom 을 비교해서 변경된 부분만 appendChild 해준다.
container.appendChild(renderElement(virtualDom));
}
const vdom = {
type: "ul",
props: { item: "acac", id: "hoho" },
children: [
{ type: "li", props: { className: "item" }, children: "React" },
{ type: "li", props: { className: "item" }, children: "Redux" },
{ type: "li", props: { className: "item" }, children: "TypeScript" }
]
};
function createElement(type, props = {}, ...children) {
if (typeof type === "function") {
return type.apply(null, [props, ...children]);
}
return { type, props, children };
}
function Row(props) {
return <li>{props.label}</li>;
}
function StudyList(props) {
return (
<ul label="하하하">
<Row label="하하하" />
<li className="item">Redux</li>
<li className="item">TypeScript</li>
<li className="item">mobx</li>
</ul>
);
}
function App() {
const vdom = createElement("ul", {}, createElement("li", {}, "React"));
return (
<div>
<h1>1</h1>
<StudyList item="acac" id="hoho" />
</div>
);
}
render(<App />, document.getElementById("root"));
React는 한개의 Virtual DOM 을 가지고 있고, render 함수를 호출 할 때 변경된 부분만 리렌더링을 해준다.
HTML built in component는 소문자로 시작하기 때문에 HTML 로 들어가지만, 사용자가 생성한 컴포넌트는 대문자로 선언하면 바벨에서 function 으로 변환해준다. 그래서 컴포넌트를 생성할 때 대문자로 시작하게끔 선언해준다.
// 사용자 컴포넌트 대문자로 사용.
render(<App />, document.getElementById("root"));
// babel transpiling. function App 을 호출하는걸로 변환되었다.
render(function App(), document.getElementById("root"));
클래스형 컴포넌트
클래스형 컴포넌트는 라이프사이클이 존재하며 상태를 생성자에서 초기화 할 수 있다. React.Component 를 상속받아서 render 함수를 무조건 구현해줘야한다.
import React from "react";
import ReactDOM from "react-dom";
class Hello extends React.Component {
constructor(props) {
super(props);
}
this.state = {
count: 1;
}
componentDidMount () {
this.setState({ count: this.state.count + 1 });
}
render() {
return <p>안녕하세요 !</p>;
}
}
상태를 변경하기 위해서는 setState 함수를 호출하고 setState 를 호출하지 않고 변경하면 리렌더링이 되지 않는다. 변경하기 위해서는 꼭 setState 함수를 호출해야 한다.
함수형 컴포넌트
function App() {
let x = 10;
return (
<div>
<h1>상태</h1>
<Hello />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
import React, { useState } from "react";
import ReactDOM from "react-dom";
class Hello extends React.Component {
constructor(props) {
super(props);
}
render() {
return <p>안녕하세요 !</p>;
}
}
function App() {
const [counter, setCounter] = useState(1);
return (
<div>
<h1 onClick={() => setCounter(counter + 1)}>상태 {counter} </h1>
<Hello />
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
초기에 함수형 컴포넌트는 상태를 갖지 못하는 컴포넌트였다. 함수가 호출될 때마다 상태가 초기화되기 때문에 유지가 될 수 없었는데 Hooks 가 등장하면서 함수형 컴포넌트도 상태를 관리할 수 있게 되었다.
최상위에서만 Hook을 호출해야 한다. 조건문에서 호출한다거나 최상위가 아닌 부분에서 호출 할 경우 배열의 hook index 에 문제가 발생하여 의도한대로 나오지 않을 수 있다.
함수형 컴포넌트는 Class 컴포넌트처럼 라이프사이클을 가질 수 없지만 useEffect 라는 Hook 을 통해 라이프사이클처럼 구현 할 수 있다.