React-Native First Impression

Facebook 이 얼마전 발표한 Javascript 로 iOS Native App 을 개발하는 React-Native 를 사용해보았다. 첫인상은 상당히 만족스럽다. 가장 기본적은 샘플 코드를 보면서 분석해보자.

Scaffolding

react-native init [프로젝트명] 으로 만들면 [프로젝트명] 폴더가 생성되는데 xcode 로 열어서 실행하면 된다.

package.json 은 node.js npm dependencies 니 넘어가고, index.ios.js 가 우리가 실제로 수정하는 부분이다. index.ios 에서 보듯이 결국 android 도 나오지 않을까 하는 생각이 든다. React Blog 에서 보니 6개월 정도 걸린다고? 오픈 소스로 만드는데 시간이 걸린다는 것 같다.

When is React Native Android coming? Give us 6 months. At Facebook, we strive to only open-source projects that we are using in production. While the Android backend for React Native is starting to work (see video below at 37min), it hasn't been shipped to any users yet. There's a lot of work that goes into open-sourcing a project, and we want to do it right so that you have a great experience when using it.

project 를 열면 아래와 같은 화면이 보인다.

React 의 약자(?)로 보이는 RCT 어쩌구 하는 Library 가 눈에 보인다. 자세히는 열어보지 않았지만, iOS API 를 Javacsript 사이의 Bridge 라이브러리로 보인다.

index.ios.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 */
'use strict';

우선 확실하진 않지만 ECMA Script 6 표준을 사용하는 것으로 보인다.

var React = require('react-native');
var {
  AppRegistry,
  Image,
  ListView,
  StyleSheet,
  Text,
  View,
} = React;

var { Image, ListVIew } = React, (ro1, row2) => row1 !== row2 라던가 하는 생소한 문법이 계속해서 보인다.

var API_KEY = '7waqfqbprs7pajbz28mqf6vz';
var API_URL = 'http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json';

var PAGE_SIZE = 25;
var PARAMS = '?apikey=' + API_KEY + '&page_limit=' + PAGE_SIZE;
var REQUEST_URL = API_URL + PARAMS;

예제가 RottenTomatoes 의 영화 리스트를 ListView 로 보여주기 위한 예제이므로 이런 값들을 상수로 넣었다.

var AwesomeProject = React.createClass({

React (Web Framework) 에서 사용하던 방식을 그대로 가져왔고, node.js 에 익숙한 사람이라면 module.exports = { method: function () {} } 이런 방식으로 사용하는 것과 유사해 보인다. 이건 Impact.js 와 같은 게임엔진에서도 보이는 방식인데, 기본 함수를 오버라이딩 (맞나 이 개념이?) 해서 실행되는 것으로 보인다. 자세한 워크 플로우는 좀 더 봐야할 것 같음.

  getInitialState: function() {
    return {
      dataSource: new ListView.DataSource({
        rowHasChanged: (row1, row2) => row1 !== row2,
      }),
      loaded: false,
    };
  },

iOS 의 경우에는 초기 상태를 지정하지 않고, 나중에 데이터를 가져와서 row 를 업데이트 하는 방식으로 만들어져있는데, 사실 처음에 접할때는 좀 난해하긴 했다. InitialState 를 명확하게 지정하는 편이 좀 더 알기 쉬운것 같다.

  componentDidMount: function() {
    this.fetchData();
  },

이건 Objective-C 의 -(void)viewDidLoad 와 유사한 함수인걸로 보인다. 물론 view 가 아니라 각 class 의 초기 상태가 완료된 완료된 상태를 말하겠지만, 어쨋든 class 하나의 측면에서 봤을때는 비슷한 의미로 받아들여도 될듯.

  fetchData: function () {
    fetch(REQUEST_URL)
      .then( (response) => response.json() )
      .then( (responseData) => {
        this.setState({
          dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
          loaded: true,
        });
      })
      .done();
  },

ECMA Script 6 에서 사용되는 방식인데 특히 => 요놈은 아직 익숙치 않아서 새로운 표준에 대해 빨리 익숙해져야 하겠다는 생각이 들었다. 또한 Promise Pattern 이 내장 함수로 만들어져있는 것은 상당히 만족스럽다.

  render: function () {
    if ( !this.state.loaded ) {
      return this.renderLoadingView();
    }

    return (
      <ListView
        dataSource={ this.state.dataSource }
        renderRow={ this.renderMovie }
        style={ styles.listView } />
    );
  },

getInitialState, componentDidMount, render 는 각각 자동으로 실행되는 함수인데, return 시키는 부분이 역시 상당히 낯선데 react 에서도 비슷한 방식을 사용했다. 특히 자주 실수를 하게 되는게 html 이라면 dataSource="{ this.state.dataSource }" 이런 방식으로 tag 를 만들텐데, "" 부분이 없어지니 상당히 혼동스럽다.

게다가 보통의 프레임워크는 return 시킬때 주로 이런 부분은 '' 로 감싸서 text 로 넘겨주는데 괄호로 감싸서 보내는 것은 낯설기도 하지만 신기하기도 하다. 시간이 되면 source 를 좀 뒤져봐야겠다. 내부적으로 jQuery 를 사용한것도 아니라서 좀 재밌다는 느낌이 들었다.

  renderLoadingView: function() {
    return (
      <View style={styles.container}>
        <Text>
          Loading movies...
        </Text>
      </View>
    );
  },

  renderMovie: function (movie) {
    return (
      <View style={ styles.container }>
        <Image
          source={{ uri: movie.posters.thumbnail }}
          style={ styles.thumbnail }
        />
        <View style={ styles.rightContainer }>
          <Text style={ styles.title }>{ movie.title }</Text>
          <Text style={ styles.year }>{ movie.year }</Text>
        </View>
      </View>
    );
  },
});

render 메소드에서 각 row 를 렌더링 하는 renderRow={ this.renderMovie } 를 지정해주었는데, 이 부분의 소스가 위와 같다. var styles 를 선언하는 부분이 createClass 바깥 부분에 있고 심지어 그 밑에 있는데다가 function 으로 선언한것도 아님에도 그냥 읽어드리는 걸 보니, 대충의 구조를 알 것 같기도 하다.

어쨋든 코딩하는 입장에서는 깔끔하게 관리할 수 있을 것 같아 좋은 면인 것 같다. 사실 뭐.. 왠만한 언어에서는 다 지원하는거긴 한데...

var styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  thumbnail: {
    width: 53,
    height: 81,
  },
  rightContainer: {
    flex: 1,
  },
  title: {
    fontSize: 20,
    marginBottom: 8,
    textAlign: 'center',
  },
  year: {
    textAlign: 'center'
  },
  listView: {
    paddingTop: 20,
    backgroundColor: '#F5FCFF',
  },
});

CSS 를 적극 활용하되 Titanium 과 유사한 방식을 사용하는 것으로 보인다.

AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);

iOS 와 Link 해주는 부분.

완성된 모습. CMD+R 을 누르면 빌드 하지 않고도 재시작된다. Titanium 을 이 기능때문에 쓰려다가 이 기능때문에 때려친 나로선 정말 큰 메리트인것 같다.

결론

Titanium 을 써보려고 할때 뭐가 그렇게 복잡한지 뷰 하나 만드는데 학습 부담이 상당했는데, React-Native 는 확실히 쉽다. 구조도 간단하고, 코드 읽기에도 부담이 매우 적다. 어디까지 만들수 있을지는 좀 더 테스트해봐야 알겠지만, 매우 만족스럽다.

다만, 기존 JS 프레임워크와 사용법이 다른 점이 약간 신경쓰이고 Hybrid 앱의 치명적인 단점인 '이게 안된다고?' 하는 문제를 얼마나 해결했는지 궁금하다. 어쨋든 좀 더 튜토리얼을 따라가보고 또 포스팅해야겠음.

마지막으로 보통의 하이브리드 앱이 Write Once, Run Anywhere 라는 듣기엔 좋지만 비현실적인 슬로건을 외치는데에 반해서, Learn Once Write Anywhere 라는 현실적인 대안을 제시하였다는게 상당히 맘에 든다.

읽어볼거리

First-Impressions-using-React-Native