JavaScript
Android
iOS
reactjs
reactnative

Web開発者、アプリ開発者に捧ぐReactの提唱する"learn once, write anywhere"はどこまで本当なのか?

More than 1 year has passed since last update.


最初に

この記事は React Native Advent Calendar 2016 23日目です。

釣りなタイトル失礼します。

以前、FacebookまじでReact Nativeやるってよ!でFacebookにとどまらず、Instagram, airbnbのアプリがReact Native製に置き換わっていることを投稿しました。

個人的に大きいニュースだと思ってます。

なぜなら安定稼働しているiOS/Androidのナレッジとチームを棄ててまでも、乗り換えを選択する理由がReact Nativeにはあると言ってるものだからです。

様々な理由があると思いますが、大きな要因は"learn once, write anywhere"でレバレッジが効いていて、


  • 優秀なWeb技術者(React)がアプリ開発に渡りやすかった

  • ナレッジがたまり易いチーム構造に変えることができた

ため、置き換えコスト < 置き換えメリットになり、実現したものだと思っています。

"learn once, write anywhere" = 「一度覚えればどこでも書ける」

この、謳い文句はどこまで本当なのでしょうか?


learn once, write anywhere の検証

ReactとReact Nativeを対比しながら、以下の観点から検証していきます。


  1. ビュー

  2. スタイル

  3. データフロー

  4. 画面遷移

  5. 外部モジュール

  6. ビルド


1.ビュー

React・・・仮想DOM(<div>はHTMLタグじゃなくてReact.Componentのdivとして扱う)

React Native・・・javascriptランタイムがネイティブコンポーネントを呼び出す(<Text>はiOS,Androidのネイティブコンポーネントを呼び出す)

Reactが仮想DOMを使用して差分描画を実現していますが、

React Nativeもjsランタイムを通すことで仮想DOMに近い形を実現しています。

Componentの差分を比較検討し、変更の際、描画処理が走ります。

React NativeのListViewを作るとよくわかります。

Itemが100あろうと、1つのアイテムに変更がある場合は、そこだけ再描画が走ります。

簡単なサンプルです。テキストボックスがあり、それをインタラクティブにラベルに反映するような場合はどのような記述になるでしょうか?

Reactでフィールドを用意し、イベントでstateを変更するコードを書いてみます。


React

constructor(props){

super(props);
this.state = {
textValue: ''
}
}

changeText(e) {
this.setState({textValue: e.target.value});
}

render(){
return(
<div>
<p>{this.state.textValue}</p>
<input type="text" value={this.state.textValue} onChange={this.changeText} />
</div>
);
}

それを踏まえてReact Nativeのコードはどうなるでしょうか?


React Native

constructor(props){

super(props);
this.state = {
textValue: ''
}
}

changeText(e) {
this.setState({textValue: e.target.value});
}

render(){
return(
<View>
<Text>{this.state.textValue}</Text>
<TextInput value={this.state.textValue} onChange={this.changeText} />
</View>
);
}

どこが変わったかわかるでしょうか?

上記ではタグの変更で、同じ結果を得ることができます。

それだけでなく、Reactの特徴である、stateやprops、初期処理(constractor)や表示前処理(componentDidMount)などのフローがReactとReact Nativeは同じです。

iOSとAndroidライフサイクルを意識することなく、Reactのコーディングで画面描画を実装することができます。

ビューで共通できるところは多いです。


React & React Nativeの共通コード


import React, { Component } from 'react';
// 必要なmoduleをimport

class App extends Component {
propTypes: {
// パラメータの検証
}

constructor(props) {
super(props);
this.state = {
// stateの初期処理
}
}

componentWillMount(){
// render前に呼ばれる
// 初期処理に適している
}

shouldComponentUpdate(){
// 描画するか否か
// trueかfalseを返す
}

render() {
return(
// Viewを返却
);
}

componentDidMount(){
// renderされてから呼ばれる。DOMの初期処理など
}

componentWillReceiveProps(nextProps){
// propsが更新される時呼ばれる
// 例えば、Reduxのstateが更新され、propsで降りてくる時に便利
}

componentWillUpdate(nextProps, nextState){
// Componentが更新される前に呼ばれる
}

componentDidUpdate(prevProps, prevState){
// Componentが更新された後に呼ばれる
}

componentWillUnmount(){
// Componentが削除される時呼ばれる
// 何かクリーンアップする時に使う
}
}

module.exports = App;

ビュー周りはここまで共通化できます。基本違いが生まれる箇所はrenderとなります。

参考:

React.jsのComponent Lifecycle

React component ライフサイクル図


2.スタイル

ReactがCSSなのは言わずもがなですが、React NativeもCSSによく似た機能を使用することができます。

体感的には、ほぼCSSです。下記を見ると、CSSで使用してたプロパティが目につくでしょう。

色、コンポーネントの配置、フォントサイズやpadding,marginまで体に染み付いたであろうプロパティの数々を使用できます。

React・・・CSS。 Componentに入れることも可能

React Native・・・StyleSheetというCSSに似た機能を使用

React Nativeの配置はFlexboxで行います。

Flexboxはfloatに変わるBox操作するCSS3のプロパティです。

仕様が固まった + シンプルさから、使うケースも増えてきたのではないでしょうか?

Flexboxが使えることで、新たな概念の学習をすることなく、またネイティブ間のスタイリングを意識することなく、Webライクに配置が可能です。


React

CSSで書くこともできますし、CSS in JSもできます。その場合はキャメルケースで書きます。こちらの方がReact Nativeの記述に近いです。

SassやBootstrapのようなCSSフレームワークを使用することもできます。

// {}でプレーンオブジェクトを作成します。

const styles = {
container: {
display: -webkit-flex,
display: flex,
flexDirection: row,
},
item: {
display: -webkit-flex,
display: flex,
flex:1,
margin: 10,
},
text: {
fontSize: 16,
color: #333333,
ontWeight: 'bold',
textDecorationLine : underline,
}
}


React Native

StyleSheet.create({})でオブジェクトを作ります。CSSライクで記述します。プロパティはキャメルケースで記述します。

const styles = StyleSheet.create({

container: {
flex: 1,
flexDirection: row,
},
item: {
flex:1,
margin: 10,
},
text: {
fontSize: 16,
color: #333333,
ontWeight: 'bold',
textDecorationLine : underline,
}
});

いかがでしょうか?ほとんど代わり映えがありません、React Nativeはビルドの際、iOS, Androidのスタイルに合わせたコンパイルをしているはずです。


3.データフロー

オブザーバーのデザインパターンを使います。代表的なのはFlux、もしくはReduxでしょう。

私はReduxを使うことが多いのですが、

簡単に言うとアプリケーションの状態(state)をグローバルで管理する箱です。

ビューのトリガーから -> アクションを起こし -> Storeが更新され -> ビューに伝播していくような一方行が特徴です。

Flux/Reduxは疎結合に作られています。(つまりビューに関与しない)

そのため、ReactとReact Nativeで同じコードを利用することができます。

Reduxとビューを紐付けるためには、react-reduxconnectを使用します。


React

import React, {Component} from 'react'

import { connect } from 'react-redux'
class Todo extends Component {
render(){
return(
.....
);
}
}

const mapStateToProps = (state) => {
return {
todos: state.todos ? state.todos : []
};
}

export default connect(
mapStateToProps,
)(Todo);


React Native


import React, {Component} from 'react';
import {,
View,
} from 'react-native';
import { connect } from 'react-redux'
class Todo extends Component {
render(){
return(
.....
);
}
}

const mapStateToProps = (state) => {
return {
todos: state.todos ? state.todos : []
};
}

export default connect(
mapStateToProps,
)(Todo);

上記のようにビューは全く同じ紐付けができます。

長くなるので省略しますが、redux自体はindexに近いところ(App.jsやindex.js)で作成されますが、

createStoreでストアを作成し、Providerでアプリ全体を囲います。その辺りの書き方、考え方もReactと同様に使用することができます。

勿論、優秀なMiddleware(redux-loggerredux-saga)も同じように使用することが可能です。

iOS、Androidにもそれぞれ培われたデザインパターンがあると思いますが、両方を把握するにはそれなりのパワーが必要です。

React + Reduxを知っていればそのまま流用できるというのは魅力的です。


4.画面遷移

Webとアプリの画面遷移(ナビゲーション)は大きな違いがあると考えています。


  • Web -> URLからURLの遷移(historyなどでで履歴を復元)

  • アプリ -> 画面を積んでいく(画面インスタンスのpush->pop)

Webでは、A -> B -> Cは関連のないURLの繋がりですが、アプリにおけるA -> B -> Cは意味のある遷移となります。当然メモリも消費します。(分かりづらくてすみません)

つまり、考え方が違うので、WebのフロントエンドのReactとアプリのReact Nativeでは違いが出てくると考えてください。問題はどこまで違いがあるかです。


  • Reactではreact-routerがデファクトスタンダートです。

  • 一方、React Native公式のNavigatorでは、画面をpush, popする方式が取られているようです。

react-routerとでは書き方がかなり異なります。しかし、Web移民が多いであろうReact Native界隈。。。

react-routerのようなroutingライクにコードを書きたいという切なる願いに対して、OSSコミッタ様は素晴らしいモジュールたちを作ってくれてます。

react-native-router-fluxもその一つです。

外部モジュールを使用することで、react-routerライクな記述を実現することができます。


React

react-routerを使用

// pathと対応するcomponentで、Routeを1箇所で宣言

render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.getElementById('root'))

// 画面遷移の方法
// Linkで宣言
<li key={user.id}><Link to={`/user/${user.id}`}>{user.name}</Link></li>


React Native

react-native-router-fluxを使用

// keyと対応するcomponentで、Routeを1箇所で宣言

render() {
return <Router>
<Scene key="root">
<Scene key="login" component={Login} title="Login"/>
<Scene key="register" component={Register} title="Register"/>
<Scene key="home" component={Home}/>
</Scene>
</Router>
}

// 画面遷移の方法
import { Actions } from 'react-native-router-flux';
// Actions.key()でメソッドを呼び出す
Actions.register({param1: '', param2: ''});

RouteSceneのタグの違い、画面遷移の方法。他、異なる部分はありますが、書き方としてはreact-routerに近い形になりました。


5.外部モジュール

同じくnpmが使えます。packge.jsonの書き方も同じです。

Reactは、awesome-react(https://github.com/enaqx/awesome-react)

React Nativeは、awesome-react-native(https://github.com/jondot/awesome-react-native)

にまとめられています。

意外かもしれませんが、React NativeでもjQuery(勿論DOM操作は除く)やlodashを使用することができます。

いわゆるユーティリティ系です。使用したことがあるhtmlperserが使用でき助かりました。

エコシステムが効いています。

ただし、ビューは別となります。ReactNativeはreact-native-・・・で始まるモジュールが多い気がします。


React

Reactの人気なモジュール

基本機能だけでなく、テストモジュール、トランスコンパイラ、処理速度向上、不変なコレクション。などなど、React本体と関わりがあるのではなく、それを支える素晴らしいモジュールが多くあるように感じます。

Componentに関しては、一つの部品として人気があるのではなく、Material-UIのようにテーマで提供されている印象です。

Categoriesも多いです。それだけフレームワークとして洗練されているのでしょう。


React Native

React Nativeの人気なモジュール

求められてるのが異なるのが大きな理由ですが、Component単位での人気があります。

つまり、本家で未実装なもの、より使いやすくしたもの。が人気が高い印象です。

React Nativeが洗練されていないのではないか?というと実は一概には言えません。ビュー以外はReactのモジュールも使えるものが多いからです。

例えば、

ビューと切り離した部分はReactと同じモジュールが使用できます。(React自体がビューのライブラリなので、そらそうだと言われればそうですがw)


6.ビルド

ビルドに関しては異なります。

Reactは、JSXをブラウザで使用するため、babelなどでプリコンパイルする必要があります。webpackでモジュールをまとめたりします。

タスクランナーに関してはgulpもしくはnpmでしょうか?

React NativeはiOS、Android各プラットフォームにビルドします。

// iOSビルド

$ react-native run-ios
// androidビルド
$ react-native run-android

ipa、apkなどの実行ファイルを作成することはわかってるのですが、

後ろでどのような処理の流れ、どのようなコンパイルをして、どのようなファイルを作成しているかは正直わかってません。

ターミナルに大量のログが出てるのをスゲーなーと眺めています(笑)

iOSをビルドするためにはXcodeが必ず必要です。

Androidをビルドするためにはシュミレータが必要です。Genymotionがよいでしょう。

対象とする環境が違うので当たり前の話なのですが、ビルド & 設定はiOS、Androidの知識が必要なところで、知らない場合は一番詰まるところだと思ってます。

Reactで書けるとはいえ、ネイティブコードを全く触らなくて良い。という訳ではありません。

Componentの自作は避けることができますが、ビルド周りや、その設定、ネイティブコードのエラーハンドリングは避けることができません。

知らないが故、生産性がガクッと落ちるというケースがあります。


React

react-scripts または、babel + webpackで内部サーバの立ち上げ、JSXのトランスコンパイルを行う。


React Native

ブラウザではないためbabel + webpackはない。

ターミナルからコマンドを叩くことでビルドする。

各プラットフォームにビルドするために、ネイティブのお作法、知識が必要。


まとめ

learn once write everywhere
所感

ビュー

Componentは変更が必要だが、考え方、ライフサイクル、構文は一緒

スタイル

CSSライクに記述可能。Componentの配置もCSS3のFlexboxが使用可能

データフロー

Flux/Reduxが同じ形式で使える。

画面遷移

同じモジュールは使えないが、記述が似ているものはある

外部モジュール

npmを使用。Componentは各モジュールを使う必要があるが、エコシステムは効いている。

ビルド
×
異なる。iOS,Androidのコンパイル方法、設定周り、ハマリどころを抑えておく必要がある

こんな感じだと思ってます。

上記の比較検討いかがだったでしょうか?

ここまで共通化できるのであればAirbnb、Facebookの優秀なReact使いがReact Nativeを使うのも納得いきます。

逆も然りです、アプリエンジニアがReact Nativeを覚えることでReactコードをメンテナンスすることが可能です。

React Nativeは7〜8割はコードを共通化できます。開発速度、メンテナンスコストの向上が見込めます。

Reactの学習でReact、React Nativeを開発が可能で、レバレッジがかなり効きくと思います。

実際に、私は、React -> React Nativeとやったのですが、

React Native -> Reactを触った時に自分のReact力がかなり上がってることに気づきました。

具体的に言うと、前に書いたReactのコードが汚く見えました。

例えば、

ビューで言うと、local state使いすぎ問題や、Componentの設計、Reduxのconnectの方法、bindをrenderでしてる。。。などなど

Reduxで言うと、actionsのコード、reducerの切り方や、redux-reselectorの採用、reduxの必要なモジュールの数々。などなどです。

React <-> React Nativeは可能で、つまり"learn once, write anywhere"は本当だと言えるでしょう。

私自身の所感となりますが、

React Nativeはまだまだ発展途上のフレームワーク故、問題、解決できないバグ、知見の少なさから、「荊だ・・・」と感じたこともありました。

ただし、それを差し引いても、以上に得られたものが非常に多く、自身を一つ上へ引き上げてくれたフレームワークだと思っています。

得た経験は、Reactで展開することができ、Webが主戦場な自分としては、アプリ開発をする場合はReact Nativeを選択することでしょう。(Reactが廃れるまでわw)


  • Reactを使っているけどアプリ開発手を出していない

  • アプリ開発したいけど、今からAndroid/iOS 両方覚えるのはコストが高い

  • Android, iOS開発を行っているけど、他に何か。。。

そんな、そこのあなた! 年末年始の機会にReact Nativeに触れてみるのはいかがでしょうか!

以上。エモい内容、長々と失礼しました。