React JS 처음 만나보기

나는 프론트엔드 기술을 잘 모른다. 주로 데이터베이스/미들웨어/임베디드 시스템 등 UI랑 별 상관없는 분야에 종사하기 때문이다.

요즘 풀스택 개발자가 미덕이라는데... 나도 요즘 뜨는 프론트엔드 하나 쯤은 알아야 할 것 같은 압박감이 없지 않았다. 그래서 내 처지에 맞는 프론트엔드 기술을 찾는 중이다.

주로 Java로 개발하기 때문에, UI를 개발할 일 있으면 데스크탑 어플리케이션인 경우 Java Swing 혹은 JavaFX, 경우에 따라서는 wxPython을 사용했고, 웹으로 개발할 일이 있으면 Apache Wicket을 사용했었다.

Wicket은 잘 알려져 있지 않은 기술인데, 동적 웹 개발을 JavaScript가 아니라 Java로 한다. 그리고 웹을 컴포넌트 기반으로 개발할 수 있다. 나에게 웹 개발이 짜증났던 이유는 재활용이 가능한 컴포넌트로 구성하기 어려웠기 때문이다. 그런데 Wicket은 익숙한 Java로 개발할 수 있고, 컴포넌트로 만들 수 있어 재활용하기에 좋았다.  Google Web Toolkit과 비슷하다고 보면 된다.

그런데 Wicket은 기본적으로 예쁘지 않다. 게다가 팀원들에게 Wicket을 써보라 권유했었는데, 모두들 포기해 버렸다. JavaScript에 익숙한 프론트엔드 기술자들에게는 Java로 웹을 코딩하는 것이 너무 버거웠던 것이다. Wicket이 코딩 양이 많긴 하다.

React를 선택하다


오래전 AJAX가 웹 생태계를 흔들어 놓았는데, 요즘은 HTML5와 막강해진 JavaScript가 대세다. 그러나 이들 기술을 그대로 사용하는 경우는 드물다. 이것들을 밑에 깔고, 개발자들이 더 편하게 개발하고 관리할 수 있도록 하는 프레임웍이 만들어졌다. 가히 춘추전국시대라 할 만하다.

게으른 나는 이런 혼돈의 시대에 어느 편을 택하지 않는다. 치열한 전쟁 후 끝까지 살아남아 그 우수함을 증명한 기술을 택하자는게 나의 게으른 개똥철학이다. 지금 2016년 12월에 대세를 이루는 두 기술은 AngularJSReact이다. AngularJS는 Google에서 출발한 것이고, React는 Facebook에서 출발한 것이니 둘 다 금수저 프레임웍이라 볼 수 있다.

AngularJS가 버전1인 시절에는 단순하고 아름다워 많이 끌렸는데, 버전2로 올라오면서 무슨 이유인지 모르겠지만 튜토리얼 조차 보기 어려울 정도로 어려워진 것 같다. 반면 React는 처음볼 때는 JSX라는 요상한 문법 때문에 거부감이 있었으나, 보면 볼수록 이 단순한 변화 하나가 참 매력적이다.

결론적으로 난 React를 택했다. React는 UI(View)만 담당하는 작은 프레임웍이다. UI를 제외한 HTTP요청, 컨트롤러, 로직 등은 모두 다른 방안을 찾아야 한다. 이건 단점이자 장점이다. 나같이 게으른 사람에게는 UI만 새로 배우면 되니, 배울게 적어 좋다. 나머지 기술 부분은 내가 그동안 쓰던 걸 쓰면 되니까.

내가 React를 택한 것은 엄밀한 비교를 통해 내린 결론이 아니다. 그냥 React에서 좋은 느낌이 왔 기 때문에 택한 것이다. 그러니 기술적 태클은 사양한다.

From Basic Difference Between AngularJS and React -- by Catalin Cimpanu

Hello, Universe


첫 예제를 고르다가 Sandeep Panda가 쓴 Getting Started with React and JSX라는 글이 좋아서, 그것을 바탕으로 정리해 본다.

어쨌거나 첫 프로그램은 Hello World다. Panda의 예제를 그대로 옮기면 다음과 같다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>My First React Example</title>
  </head>
  <body>
    <div id="greeting-div"></div>

    <script src="https://unpkg.com/react@15/dist/react.min.js"></script>
    <script src="https://unpkg.com/react-dom@15/dist/react-dom.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.18.1/babel.min.js"></script>
    <script type="text/babel">    // or "text/jsx"
      var Greeting = React.createClass({
        render: function() {
          return (
            <p>Hello, Universe</p>
          )
        }
      });

      ReactDOM.render(
        <Greeting/>,
        document.getElementById('greeting-div')
      );
    </script>
  </body>
</html>

스크립트 로딩하는 부분에서 react.js와 react-dom.js는 React 프레임웍의 핵심 모듈이다. 그리고 이어서 로딩하는 browser.min.js는 babel 모듈이다.  이 모듈이 뭐하는 녀석인지는 좀 있다 살펴보자.

그 아래 있는 JavaScript 코드는 뭔가 이상한 점이 보인다. Greeting이라는 객체를 만드는데(React.createClass) render라는 함수를 정의한다. 이 함수는 그냥 HTML 태그를 리턴한다. 그런데 일반 JavaScript처럼 따옴표를 싸지 않고 그냥 태그를 쓴다.

처음에는 이게 이상했다. 뭔가 허전하고 규칙에 어긋나는 듯한 느낌... 하지만 따옴표로 쌀 때와 아닐 때를 비교해보면 깔끔함에서 차이가 난다. 이 매력에 빠지면 헤어날 수 없다.

이렇게 객체로 정의된 Greeting은 그 아래에서 볼 수 있듯이 태그로 쓰일 수 있다. 이것이 바로 React가 컴포넌트를 만드는 방법이다. 뷰를 담당하는 객체를 만들고 그것을 그냥 태그로 불러 쓰는 것이다. 얼마나 깔끔한가?

위 예제를 실행하려면 적당한 웹서버의 루트에 두기만 하면 된다. 서버측에서 처리하는 코드가 없기 때문에 정적파일을 처리하는 웹서버에 두면, 브라우저가 가져와서 실행하는 방식이다.

JSX는 어떻게 브라우저에서 실행되는가?


위에서 봤던 JavaScript와 HTML이 섞인 문법을 JSX라고 한다. 이 문법은 브라우저가 해석하지 못한다. 그런데 어떻게 브라우저에서 실행되는 것일까? 그 비밀은 바로 앞서 로딩했던 babel 모듈에 있다.

원래 babel은 브라우저마다 다른 JavaScript의 버전 차이를 완화하기 위해 최신 JavaScript 문법으로 작성한 코드를 브라우저에 맞게 번역해주는 역할을 한다. 그런데 babel은 JSX도 브라우저가 이해하는 JavaScript로 바꿔줄 수 있다. 즉 위의 코드가 브라우저에 로드되면 babel은 script type이 text/babel인 영역을 찾아 JavaScript로 바꿔준다.

예를 들어 위의 JSX는 다음과 같이 바뀐다. (ES5인 경우)

var Greeting = React.createClass({
  render: function() {
    return React.createElement("p", null, "Hello, Universe");
  },
});

ReactDOM.render(
  React.createElement(Greeting),
  document.getElementById('greeting-div')
);

물론 이와 같이 명시적인 JavaScript를 선호한다면 babel을 쓰지 말고, 이렇게 코딩해도 된다. 하지만 가독성을 생각하면 JSX가 훨씬 더 낫다는데 동의할 것이다.

Props는 위에서 아래로 흐른다


위에서 Greeting이라는 컴포넌트를 만들었었다. 그런데 Greeting은 항상 같은 결과를 나타내는 상수 컴포넌트다. 현실의 컴포넌트는 같은 로직을 가지고 있지만 데이터가 달라 다른 모양을 나타낸다. 이를 묘사한 것이 props이다.

위의 JSX 부분을 아래와 같이 바꿔보자.

var Greeting = React.createClass({
  render: function() {
    return (
      <p>{this.props.message}</p>
    );
  }
});

setInterval(function() {
  var messages = ['Hello, World', 'Hello, Planet', 'Hello, Universe'];
  var randomMessage = messages[Math.floor((Math.random() * 3))];

  ReactDOM.render(
    <Greeting message={randomMessage}/>,
    document.getElementById('greeting-div')
  );
}, 2000);

"Hello, Universe"를 찍던 Greeting 컴포넌트의 출력 내용이 {this.props.message}를 출력하는 것으로 바뀌었다. message라는 props의 내용을 찍으라는 의미라는 건 선수라면 알 것이다. 그렇다면 이 message는 어떻게 설정하는 것일까?

아래 태그의 속성으로 message가 있음을 볼 수 있다. 물론 위 코드에서는 한번 더 꼬아서 2초마다 한번씩 랜덤으로 메시지를 골라 설정하게 되어 있다. 즉 message prop이 2초마다 바뀐다. 

이렇게 컴포넌트를 쓰는 쪽에서 태그의 속성(attribute)로 지정한 것이 바로 컴포넌트 입장에서는 this.props 안에 있는 것이다.

props를 바꾼다고 실제 뷰가 바뀌는 건 아니다. 실제 뷰에 반영시키기 위해서는 위의 코드처럼 해당 부분의 render 함수를 불러주어야 한다.

여기서 중요한 점은 props는 위에서 컴포넌트를 만들때 정해주는 것으로, 이후에는 변경하지 못한다는 것이다. 아래 그림처럼 윗 컴포넌트에서 아래 컴포넌트를 만들때 생성자(constructor)의 인자로 주는 것이라고 생각하면 쉽다.

From Introduction to ReactJS -- by Dinh Hoang Long
AngularJS는 양방향 데이터 바인딩을 지원하지만, React는 이렇게 단방향으로 제한되어 있다. 서로 장단점이 있다. 하지만 분명한 사실은 단방향이라서 단순하다는 점이다. 복잡한 코드에서 양방향 바인딩이 되어있다 보면 길을 잃어버리는 경우가 허다하다.

State는 컴포넌트의 상태이다


이제 어떤 버튼을 누르면 메시지가 바뀌도록 바꾸어보자. JSX 코드는 다음과 같다.

var RandomMessage = React.createClass({
  getInitialState: function() {
    return { message: 'Hello, Universe' };
  },
  onClick: function() {
    var messages = ['Hello, World', 'Hello, Planet', 'Hello, Universe'];
    var randomMessage = messages[Math.floor((Math.random() * 3))];

    this.setState({ message: randomMessage });
  },
  render: function() {
    return (
      <div>
        <MessageView message={ this.state.message }/>
        <p><input type="button" onClick={ this.onClick } value="Change Message"/></p>
      </div>
    );
  }
});

var MessageView = React.createClass({
  render: function() {
    return (
      <p>{ this.props.message }</p>
    );
  }
});

ReactDOM.render(
  <RandomMessage />,
  document.getElementById('greeting-div')
);

아래부터 보면 RandomMessage라는 컴포넌트를 렌더링하는데, RandomMessage의 render 함수를 보면 RandomMessage, div, MessageView, input 등의 컴포넌트로 구성되어 있다. 이 중에서 input의 onClick prop에 this.onClick 함수를 넣는 걸 볼 수 있다.

input 컴포넌트는 클릭 이벤트가 발생할 때 onClick prop에 지정된 함수를 호출하게 되어 있다. 호출된 RandomMessage의 onClick은 메시지를 만든 다음 this.setState() 호출을 통해 state를 변경한다. 이 state는 컴포넌트의 변경 가능한 상태를 의미한다.
그런데 컴포넌트 내부에서 정의하는 일반적인 변수(var)와 다른 점은 state가 변경되면 React에 의해 자동으로 render()가 호출된다는 점이다.  즉 state가 변경되면 실제 뷰에도 즉시 반영된다.  이것이 가장 큰 차이다.  더불어 getInitialState() 라는 함수가 생성자 비슷한 역할을 한다는 걸 추측할 수 있다.

From Introduction to ReactJS -- by Dinh Hoang Long

위 그림은 state를 이해하는데 큰 도움을 줄 것이다. 하위 컴포넌트에 props로 등록된 상위 컴포넌트의 함수(콜백)에서 자신의 state를 바꾸면, 자신을 포함하여 하부까지 뷰의 렌더링이 다시 실행된다는 것이다.

흔히들 props는 아래로 내려가고, state는 위로 올라간다고 얘기하는데, 정확하게 말하면 props는 위에서 컴포넌트를 생성할 때 전해주는 상수 인자이고, state는 컴포넌트가 가지는 상태로서 변경되면 자동으로 render()를 호출하며, 콜백 props에 상위 컴포넌트의 함수를 등록함으로서 윗쪽으로 이벤트를 전달할 수 있고, 상위 컴포넌트의 state를 바꿀 수 있는 것이다.

중요하지만 간단히 짚고 넘어가는 것들


HTML에서 input 태그의 클릭 이벤트 핸들러는 onclick이다. 그런데 위 코드에서는 onClick을 사용했다. 사실 JSX에 쓰인 input을 비롯한 HTML에서 볼 수 있는 태그들은 모두 React에 정의된 컴포넌트들이다. 그리고 코드에서 브라우저의 DOM을 조작하는 것 같지만, 실제로는 React 런타임 내부에 있는 Virtual DOM을 조작한다. 그래서 속성의 이름이 조금씩 다르다.

Virtual DOM은 React의 큰 장점이지만, 다음 기회에 알아본다.

컴포넌트는 재사용이 목적이고, 그렇다면 이렇게 어플리케이션 로직에 섞여있으면 곤란하다. 뷰 자체로서 독립된 컴포넌트가 있다면 별도의 jsx 파일로 만들고 다음과 같이 불러들이면 깔끔하다.

<script type="text/babel" src="src/random-message.jsx"></script>

이 글의 코드는 babel을 통해 브라우저에서 JSX를 JavaScript로 변환했지만, 성능에 민감한 경우에는 서버측에서 미리 컴파일하는 것이 좋다. babel-cli 모듈을 빌드 프로세스에 넣으면 되며, 이를 서버측 렌더링이라고 한다.


참고할 만한 자료들
  - Awesome React

댓글 없음:

댓글 쓰기

인기글