React.js는 UI 컴포넌트를 구성하기 위한 라이브러리 입니다. 자바스크립트 내에 DOM(Document Object Model) Tree와 같은 구조체를 VIRTUAL DOM으로 갖고 있습니다. 다시 그릴 때는 그 구조체의 전후 상태를 비교하여 변경이 필요한 최소한의 요소만 실제 DOM에 반영합니다. 따라서 무작위로 다시 그려도 변경에 필요한 최소한의 DOM만 갱신되기 때문에 빠르게 처리할 수 있습니다. React.js는 단방향 데이터 흐름을 지원합니다. 따라서 Angular.js의 양방향 데이터 바인딩을 할 수 없으나 관리하기가 더 쉬워 집니다.
DOM은 동적 UI에 최적화 되어 있지 않기 때문에 브라우저의 DOM에서 변화가 일어나면 CSS를 다시 연산하고, 레이아웃을 구성하고, 웹 페이지를 다시 가져오는데 시간을 허비하게 됩니다. 만일 동적 데이터를 다루는 요소가 많아지면 성능이 저하되게 됩니다. 물론 필요한 부분은 jQuery등을 사용해 보정할 수 있으나 동적 데이터에 대한 근본적인 처리가 되지 못합니다. 레이아웃을 새로 구성하는 것을 feflow라고 하고 레이아웃에 관계없는 색상 표현의 변경같은 것은 repaint라고 하는데 다음 예제를 살펴 봅시다.
style.padding = "20px"; // reflow, repaint
style.border = "10px solid red"; //reflow, repaint
style.color = "blue"; // repaint
style.backgroundColor = "#ffa"; // repaint
style.fontSize = "1em"; // reflow, repaint
// reflow, repaint
document.body.appendChild(document.createTextNode('hello world!'));
이 외에도, 변경하지 않고 DOM 의 값을 읽을떄도 reflow가 계산 될 때가 있습니다. element.offsetLeft, element.clientWidth, element.getClientRects() 이런 코드가 실행 될 떄도 reflow가 실행 될 수 있습니다.
React.js에서는 JSX라고 하는 XML과 비슷한 문법을 이용할 수 있습니다. 이는 선택적으로 사용할 수 있는 문법이므로 JSX가 마음에 들지 않는다면 자바스크립트로 작성할 수도 있습니다. 하지만 사용하지 않으면 좀 불편할 수 있습니다. JavaScript에 마크업 코드(Markup Code)를 작성할 수 있게 허용합니다.
제 HTML의 DOM이 아닌 React에서 사용하는 Virtual DOM이란 것이 존재합니다. Virtual DOM에는 ReactElement, ReactElement Factory, ReactNode, ReactComponent 그리고 ReactComponent Class가 있습니다. 자세한 내용은 링크(https://reactjs.org/docs/glossary.html)에서 확인 가능합니다.
정리하면 React JS를 사용하는 이유를 한마디로 말하자면 "지속해서 데이터가 변화하는 대규모 에플리케이션을 구축할 수 있다" 입니다. React에서 Component를 사용하여 웹 UI를 구축하며 데이터가 변하는 Component만을 다시 rendering해서 변화된 데이터를 표시합니다.
React를 시작하려면 Node.js와 NPM등 이것 저것 설정해야 합니다. 간단히 이용해 보려면 WebpackBin 서비스에서 Boilerplates를 누르고 React를 선택해 사용해 볼 수 있습니다. react를 선택하면 index.html, main.js, Hello.js 파일이 생성됩니다.
import React from 'react'
function Hello () {
return (
<h1>Hello from React</h1>
)
}
export default Hello
코드의 상단에선 React 를 import 했습니다. 이 import 는 공식적으로 업데이트된 자바스크립트 문법인 ECMAScript2015(ES6) 의 문법이며, var React = require('react');
와 동일한 의미 입니다. webpack 이라는 도구를 사용하여 마치 Node.js 에서 require 하는것과 같이 모듈을 불러올 수 있게 하는 것 입니다. webpack 은 이렇게 import(혹은 require) 한 모듈들을 불러와서 한 파일로 합칩니다. 이 작업을 번들링(bundling) 이라고 합니다.
import 하단에 있는 코드는 Stateless Functions 를 통하여 HelloWorld 라는 컴포넌트를 선언하는 코드입니다.
return (<h1>HelloWorld</h1>)
이런식으로 HTML 같은 코드가 그냥 적혀있는데 이러한 방식이 JSX 코드입니다. 이 코드는 webpack 에서 번들링 과정을 거치면서 webpack 에서 사용하는 babel-loader 를 통하여 JavaScript 로 변환됩니다. 위 JSX 코드가 JavaScript 로 변환되면 다음과 같습니다.
return React.createElement(
"h1",
null,
"Hello from React"
);
이제 main.js를 살펴봅시다.
import React from 'react'
import {render} from 'react-dom'
import Hello from './Hello'
render(<Hello />, document.querySelector('#app'))
HelloWorld.js 에서 만든 컴포넌트를 여기서 불러와서 페이지에 렌더링합니다. React 컴포넌트를 페이지에 렌더링 할 때에는 react-dom 모듈을 불러와서 render 함수를 통하여 처리합니다. 여기서 render 함수의 첫번째 파라미터는 렌더링 할 JSX 형태 코드입니다. 여기서는 HelloWorld 컴포넌트를 렌더링하도록 설정하였습니다. 이런식으로, 컴포넌트를 만들면 <컴포넌트이름/> 형태로 HTML 태그를 작성하듯이 쓸 수 있습니다. 두번째 파라메터는 index.html에서 찾아볼 수 있습니다.
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>
</html>
여러개의 component를 이용해 DOM을 구성하고 상위 component에서 데이터를 받아 this.props.*를 통해 접근하여 표시하는 예제로 React JS에서 Data Flow는 상위 component에서 하위 component로 진행됩니다.
.comment-box{
border: 1px solid black;
padding: 10px;
margin: 5px;
}
.comment-form{
background: #ddd;
padding: 5px;
margin: 5px;
}
.comment{
background: #eee;
padding: 5px;
margin: 5px;
}
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" href="main.css">
</head>
<body>
<div id="app"></div>
<script src="main.js"></script>
</body>
</html>
import React from 'react'
import {render} from 'react-dom'
var data = [
{author: "Youngdeok Hwang", text: "Hello~ This is a first comment"},
{author: "Heejin Hwang", text: "This is a second comment"}
];
var CommentBox = React.createClass({
render: function() {
return (
<div className="comment-box">
<h2>Comments</h2>
<CommentList {...this.props} />
<CommentForm />
</div>
);
}
});
var CommentList = React.createClass({
render: function() {
var comments = this.props.data.map(
function(comment, index) {
return <Comment author={comment.author} key={index}>{comment.text}</Comment>
}.bind(this));
return (<div className="comment-list">{comments}</div>);
}
});
var CommentForm = React.createClass({
render: function() {
return (
<div className="comment-form">
<form>
<input type="text" placeholder="Your Name" ref="author"/>
<input type="text" placeholder="Comments..." ref="text"/>
<input type="submit" value="Post" />
</form>
</div>
);
}
});
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h3 className="commentAuthor">
{this.props.author}
</h3>
{this.props.children}
</div>
);
}
});
render(<CommentBox data={data} />, document.querySelector('#app'))
위의 코드를 보면 ...this.props를 사용하고 있는데, 이는 상위 component에서 받은 props들을 그대로 하위 component로 넘기겠다는 의미입니다. 전부 전달 하는 것 외에도 몇개를 현재 component에서 사용하고 나머지를 넘기는 것도 가능합니다.
이번 예제는 this.state를 이용해 데이터를 변경하고 변경된 component만 re-rendering하는 방법을 알 수있는 예제입니다. main.js 부분만 바뀝니다.
import React from 'react'
import {render} from 'react-dom'
var data = [
{author: "Youngdeok Hwang", text: "Hello~ This is a first comment"},
{author: "Heejin Hwang", text: "This is a second comment"}
];
var CommentBox = React.createClass({
getInitialState: function(){
return {data: this.props.data};
},
dataChange: function(author, text){
var tmpData = this.state.data;
tmpData.push({
author: author,
text: text
});
this.setState({data:tmpData});
},
render: function() {
return (
<div className="comment-box">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm formHandler={this.dataChange} />
</div>
);
}
});
var CommentList = React.createClass({
render: function() {
var comments = this.props.data.map(function(comment, index){
return <Comment author={comment.author} key={index}>{comment.text}</Comment>
}.bind(this));
return (
<div className="comment-list">
{comments}
</div>
);
}
});
var CommentForm = React.createClass({
formOnChange: function(e){
e.preventDefault();
var author = this.refs.author.value;
var text = this.refs.text.value;
if(author!="" && text!=""){
this.props.formHandler(author, text);
this.refs.author.value = '';
this.refs.text.value = '';
}
else{
alert("Fill author and text");
}
},
render: function() {
return (
<div className="comment-form">
<form onSubmit={this.formOnChange}>
<input type="text" placeholder="Your name" ref="author"/>
<input type="text" placeholder="Comments..." ref="text"/>
<input type="submit" value="Post" />
</form>
</div>
);
}
});
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
render(<CommentBox data={data} />, document.querySelector('#app'))
이처럼 this.state를 이용하면 데이터를 변경하고 실시간으로 이를 표시할 수 있습니다.
CommentForm의 변화된 요소를 CommentList에 나타내려면 CommentBox에 state를 두어야 합니다. 먼저 getInitialState Lifecycle Method를 이용해 초기 state를 초기화 합니다. 이후 data state를 변경할 dataChange함수를 만듭니다. author와 text를 받아오면 기존 state에 데이터를 추가하고 this.setState() 함수를 호출합니다. React JS에서 this.state를 직접적으로 변경하면 데이터는 변경 되나 rendering이 되지 않아 데이터 변화를 표시할 수 없습니다. 하지만 this.setState() 함수를 이용하면 state 값을 변경하고 변경된 component를 re-rendering합니다. 마지막으로 이 함수를 commentForm의 props로 넘겨줍니다.
form submit 이벤트가 발생하면 input 태그의 값을 가져와 this.props.formHandler 함수를 사용할 함수를 만듭니다. 만든 함수를 onSubmit 이벤트에 바인딩하고 form이 submit이 되면 input 태그에 붙어있는 ref값을 이용해 DOM을 선택하고 가져옵니다. 이후 this.props.formHandler함수가 차례로 호출 되면서 this.setState에 의해 변화된 DOM에만 re-rendering이 일어납니다.
React Native는 백그라운스레드에서 자바스크립트를 실행하며 네이티브 레이아웃을 다루게 되었습니다. 자바스크립트와 메인 UI는 비동기로 동작하며 서로 배치 작업을 통해 UI 성능에 병목 현상을 제거하였습니다. React Native는 ReactJS를 그대로 모바일로 가져와 사용한다는 점이 특징인데 다른 점이 있다면 HTML 앨리먼트 대신에 네이티브 컴포넌트 이름을 사용할 수 있다는 점입니다.
var MoviesApp = React.createClass({
render : function() {
return (
<NavigatorIOS
style={styles.container}
initialRoute={{title: 'Movies', components: <SearchScreen />}}
routeMapper={MoviesRouteMapper}
/>
);
}
});
위의 코드를 보면 React와 비슷한 형태를 취하고 있으면서도 iOS의 네이티브 컴포넌트를 그대로 사용하고 있다는 점 입니다. 일단 한번 배우고 나면 클라이언트, 서버 혹은 모바일 앱에서도 사용할 수 있다는 "Learn Once, write anywhere"를 특징으로 하고 있습니다.
<View>, <Text>, <Image>
등 일부 공통 컴포넌트가 있기는 하지만 기본적으로 React는 네이티브 컴포넌트를 그대로 가져온다는 것을 컨셉으로 하고 있습니다. 모든 플랫폼에 항상 똑같은 컴포넌트를 제공해준다는 것은 아주 어렵기도 하지만 공통적이지 않은 네이티브 컴포넌트의 기능에 제약을 두어야 합니다. 따라서 이처럼 아예 네이티브 컴포넌트로 접근하게 만든 전략은 여러 플랫폼을 지원해야 하는 라이브러리로서 좋은 선택입니다.
또 하나 화면을 스타일링 하기 위해 CSS를 그대로 사용해 꾸밀 수 있도록 하고 있습니다.
var styles = StyleSheet.create({
textContainer: {
flex: 1,
},
movieTitle: {
flex: 1,
fontSize: 16,
fontWeight: 'bold',
marginBottom: 2,
textAlign: 'center'
},
...
});
다음 동영상을 통해 좀더 자세히 알아봅시다.
이 밖에도 React는 이해하기 쉬운 단방향 데이터 바이딩 기능 등을 제공하고 있습니다. 이런 기능을 지원하는 React의 컨셉 흔히 사용되는 MVC 패턴에서 View에만 집중하는 모델입니다.
React는 AngularJS 또는 BackboneJS와 비교되는 경우가 많지만 다른 프레임워크들이 MVC를 모두 다루겠다는 목표를 가진 것에 비해 React는 View에만 집중하고 있습니다. 초기의 React는 0.xx버전에서 갑자기 v15가 되었는데 상업적 production 사이트에도 적용 가능해도 안정적이라는 것을 보여주는 버전 네이밍입니다.
AngularJS 등의 자바스크립트 프레임워크는 검색엔진최적화(SEO)에서 검색엔진이 자바스크립트를 실행할 수 없기 때문에 컨텐츠를 검색하는데 문제가 있습니다. 또 큰 문제는 최초 접속 시 HTML를 전부 보내는 것이 아닌 클라이언트 라이브러리가 필요한 데이터를 서버에서 모두 가져와서 렌더링하는 시간이 필요하게 됩니다. 따라서 더 느릴 수도 있게 됩니다. 이를 해결하기 위해 동형 자바스크립트(Isomorphic JavaScript)가 2013년에 널리 알려지기 시작하는데 이름에서처럼 클라이언트와 서버가 같은 언어를 사용하며 코드를 공유하여 좀더 빠른 결과물을 만들어 내게 됩니다.
"어떤 것을 완전히 알려거든 그것을 다른 이에게 가르쳐라."
- Tryon Edwards -