사용자 인터페이스 구축을 위한 JavaScript 라이브러리
선언적인 것과 컴포넌트 기반인 것이 왜 중요한지 알기위해 다음의 예제를 살펴보자.
위의 예제는 간단한 문구와 카운터를 포함한다.
이를 바닐라 javascript로 구현한다면 다음과 같을 수 있다.
<div id="app"></div>
<script type="text/javascript">
const headerElement = document.createElement('div');
const textElement = document.createElement('h1');
const textElementText = document.createTextNode('Thanks React! 🚀');
textElement.appendChild(textElementText);
headerElement.appendChild(textElement);
const button = document.createElement('button');
let likes = 0;
const buttonText = document.createTextNode(`Likes ${likes}`);
button.appendChild(buttonText);
button.addEventListener('click', () => {
likes += 1;
buttonText.nodeValue = `Likes ${likes}`; // DOM node를 직접 변경
});
headerElement.appendChild(button);
const app = document.getElementById('app');
app.appendChild(headerElement);
</script>
온라인 코드 에디터에서 위의 코드로 예제를 확인해 볼 수 있다.
라이브러리 없이 자바스크립트만으로 인터랙티브 앱을 만든다면 DOM API를 사용하여 명령적으로 구현해야 한다.
엘리먼트를 만들자!
만약 앱의 크기가 커지고 상호작용이 많아진다면 직접 DOM API를 사용하여 처리하는 것은 힘들어지고 비효율적이게 된다.
이런 문제점을 해결하고자 리액트는 선언적으로 UI를 관리한다
<div id="app"></div>
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script type="text/javascript">
const textElement = React.createElement('h1', null, 'Thanks React! 🚀');
const buttonElement = React.createElement(() => {
const [likes, setLikes] = React.useState(0); // DOM node를 알아서 변경
return React.createElement('button', { onClick: () => { setLikes(likes + 1) }}, `Likes ${likes}`);
});
const headerElement = React.createElement(React.Fragment, null, [textElement, buttonElement]);
const app = document.getElementById('app');
ReactDOM.render(headerElement, app);
</script>
리액트는 createElement(type, props, children)
함수를 통해 UI를 선언적으로 처리하게 해준다.
엘리먼트를 만들자!
뿐만 아니라, render(reactNode, domNode)
함수를 통해 데이터가 변경될 때 올바른 컴포넌트만 효율적으로 알아서 업데이트한다.
이를 통해, 개발자는 데이터가 변경되는 경우에 UI를 변경하는 것을 직접 관리하지 않게 된다.
또한, 리액트는 JSX를 사용한 컴포넌트 기반으로 복잡한 UI를 만든다
JSX란 Javascript를 확장한 문법으로, HTML과 javascript를 함께 표현하도록 한다.
<div id="app"></div>
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.21.1/babel.min.js"></script>
<script type="text/javascript">
const Header = () => {
const [likes, setLikes] = useState(0);
return (
<>
<h1>Thanks React! 🚀</h1>
<button onClick={() => setLikes(likes + 1)}>
Likes ({likes})
</button>
</>
);
}
const HomePage = () => {
return (
<>
<Header />
<Header />
</>
);
}
const app = document.getElementById('app');
ReactDOM.render(<HomePage />, app);
</script>
캡슐화된 컴포넌트를 통해 HTML, CSS, Javascript 코드를 한 위치에서 관리할 수 있으며, 코드의 재사용성을 높일 수 있다.
리액트는 어떻게 데이터가 변경될 때 마다 올바른 컴포넌트만 효율적으로 업데이트하고 렌더링할까?
리액트의 라이프사이클을 이해하여 이를 알아보자.
위의 사진을 보면 리액트의 라이프사이클에는 2가지 phase가 존재하며, 각각의 역활은 다음과 같다.
Virtual DOM 이라는 새로운 개념이 나타났다. 2가지 phase를 살펴보며 이에 대해 알아본다.
위의 예시에서 살펴본 textElement
를 확인해보면, 우리가 createElement
함수로 정의한 type
, props
등을 확인할 수 있다.
// textElement Component
const textElement = React.createElement('h1', null, 'Thanks React! 🚀');
// textElement Virtual Dom
{
$$typeof: Symbol(react.element)
key: null
props: {children: 'Thanks React! 🚀'}
ref: null
type: "h1"
_owner: null
}
이와 같이, Virtual DOM이란 javascript의 Object로 DOM을 표현한 것이다.
Render phase에서는 이처럼, 우리가 작성하는 컴포넌트의 JSX를 Virtual DOM으로 변환한다.
리액트가 효율적으로 DOM을 업데이트 한다는 것은 이 Virtual DOM을 활용하기 때문이다.
DOM에서 직접 변경사항을 적용하는 것이 아니라, 렌더링 시점에 Virturl DOM
을 생성하여 이전 렌더링 시점에 생성된 Virtual DOM과 비교한다.
리액트에서는 이를 Reconcilation이라고 부르며, 비교하는 알고리즘은 다음과 같다.
그리고 위의 과정은 렌더링이 발생한 컴포넌트의 하위 컴포넌트로 재귀적으로 이루어진다.
리액트는 이처럼 top-down 방향의 one way data flow를 가진다.
리액트에서 이러한 렌더링의 트리거는 위의 라이프사이클 사진에서와 같이 세 가지가 존재한다.
Render phase에서 virtual DOM을 통해 변경이 일어난 컴포넌트를 확인했다면, Commit phase에서는 DOM에서 변경이 일어난 컴포넌트들과, 해당 컴포넌트들의 자식 컴포넌트들을 업데이트 시켜준다.
리액트 렌더링 라이프사이클을 보면, 렌더링은 다음의 경우에 트리거된다.
렌더링이 트리거되는 경우에 다음과 같은 불필요한 리렌더링이 발생할 수 있으며, 각각의 최적화 방법은 다음과 같다.