やってみた
- elm-native-uiを使ってelmでネイティブアプリhelloをしてみる
- elm-native-uiがどういう理屈でreact-nativeで動くのか少し覗く
elm-native-ui
- elmでスマホアプリが作れるもの
- react-nativeを利用している
- まだWIP
- elm-packageでダウンロードできない
引っかかりそうなところ(ぼくがひっかかった)
Sierraの人 -> https://github.com/facebook/react-native/issues/9309
elmでiosのアプリを作ってみる
手順は
- react-nativeのプロジェクトをつくる
- プロジェクト内にelmの環境を作る
- githubからelm-native-uiをclone、プロジェクトにソースコードをコピー
- elm-package.jsonを編集
- viewにelm-native-uiを使っていつものelmアプリケーションを書く
- jsファイルにコンパイルする
- index.ios.jsへコンパイルしたファイルをrequireしてメソッドを実行するコードを記述
react-nativeプロジェクトの作成
react-native cliのダウンロード
npm install -g react-native-cli
react-nativeプロジェクトの作成
コマンドを実行したディレクトリにelmNativeAppディレクトリが作成されるはず
その中でnpmインストール
react-native init elmNativeApp
~/elmNativeApp npm i
elmの環境をelmNativeAppディレクトリ以下に作る
~/elmNativeApp elm-package install
elm-native-uiパッケージのダウンロード
- elmのパッケージ「elm-native-ui」を利用するが、これはelm-packageでダウンロードしてこられない。なのでgithubから「elm-native-ui」をクローンしてくる
- ダウンロードしてきたプロジェクトのsrcディレクトリを丸ごとelmNativeAppへ移動させる
git clone https://github.com/ohanhi/elm-native-ui.git
cp -r elm-native-ui/src elmNativeApp/elm-native-ui
elm-native-uiを自分のソースコードからimportできるように設定する
elm-package.jsonを編集
-
source-directoriesに先ほどコピーしてきたelm-native-uiのディレクトリを追加
-
repositoryをhttps://github.com/elm-native-ui/elm-native-ui.git に変更
-
elmでpublishされてないモジュールを扱うための作法を省略するため(ほんらいこうはしない)
-
お作法
{
"version": "1.0.0",
"summary": "..",
"repository": "https://github.com/elm-native-ui/elm-native-ui.git",
"license": "BSD3",
"source-directories": [
".",
"./elm-native-ui"
],
"exposed-modules": [],
"dependencies": {
"elm-lang/core": "5.0.0 <= v < 6.0.0",
"elm-lang/html": "2.0.0 <= v < 3.0.0"
},
"native-modules": true,
"elm-version": "0.18.0 <= v < 0.19.0"
}
アプリケーションになるコードを書いて行く
ここではApp.elmという名前でプロジェクトルート直下に作る
module App exposing (..)
import NativeUi
import NativeUi.Elements as Elements exposing (..)
import NativeUi.Style as Style
-- MODEL
type alias Model =
String
initialModel : Model
initialModel =
"Hello"
-- UPDATE
type Msg = NoMsg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
model ! []
-- VIEW
view : Model -> NativeUi.Node Msg
view model =
Elements.view
[ NativeUi.style [ Style.alignItems "center" ] ]
[ Elements.view
[ NativeUi.style
[ Style.height 64
, Style.width 64
, Style.marginBottom 30
]
]
[]
, text
[]
[ NativeUi.string model ]
]
-- PROGRAM
main : Program Never Model Msg
main =
NativeUi.program
{ init = initialModel ! []
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
ポイント
- viewの定義、webでは
view : Model -> Html Msg
という定義になるがnativeではview : Model -> NativeUi.Node Msg
になる - mainに定義にはNativeUi.programを使うことになる
-
import NativeUi
でNativeのviewを定義するためのライブラリをimportする - NativeUi.Events, NativeUi.Style 等でUIのイベントハンドラやスタイルを定義するためのライブラリを扱える
elmのソースコードをJavaScriptへコンパイルする
elm-make App.elm --output App.js
elm-make でコンパイルしますが出力はjsファイルで!
App.jsが生成されていることでしょう
react-nativeのindex.ios.jsからelmの生成したjsファイルを呼び出す
react-native init elmNativeApp
した時点で生成されているindex.ios.jsを編集する。
const { AppRegistry } = require('react-native');
const Elm = require('./App');
const elmApp = Elm.App.start();
AppRegistry.registerComponent('elmNativeSample', () => elmApp);
実行
npm run start
react-native run-ios
elmのコードでネイティブアプリが動いた!嬉しい!
もうちょっとうごくもの
— ス (@4245Ryomt) 2016年12月17日
なんで動いてんだ!
作ったコードを見つめる
react-nativeでは
AppRegistry.registerComponent
の第二引数の関数が返すReactComponentが描画される。
const { AppRegistry } = require('react-native');
const Elm = require('./App');
const elmApp = Elm.App.start();
AppRegistry.registerComponent('elmNativeSample', () => elmApp);
つまりElm.App.start();
はReactComponentを返しているんだろう!
mainに渡されているNativeUi.programを見に行けば動く理屈がわかるはず!
main : Program Never Model Msg
main =
NativeUi.program
{ init = initialModel ! []
, view = view
, update = update
, subscriptions = \_ -> Sub.none
}
ソースコードをなぞる
NativeUi.program
の定義を求めてソースコードへ、programの定義はNativeモジュールのprogramという関数だった。
program :
{ view : model -> Node msg
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, init : ( model, Cmd msg )
}
-> Program Never model msg
program stuff =
Native.NativeUi.program stuff
NativeUiのNativeモジュールへ
NativeUiのNativeモジュールディレクトリのNativeUiディレクト内のNativeUi.jsを見る。
function start()
これを実行するとmakeComponent(impl)
の結果が返ってくる。
makeComponent
がAppRegistry.registerComponent
に渡されるReactComponentを返すに違いない!
function program(impl) {
return function(flagDecoder) {
return function(object, moduleName, debugMetadata) {
object.start = function start() {
return makeComponent(impl);
};
};
};
}
makeComponentの定義を見ると中でReact.createClassが!!
componentDidMountの中見ると
_elm_lang$core$Native_Platform.initialize(...)
elm-nativeで作るReactComponentのツリーの最上位ではhtmlを描画しないelmが回っていた
function makeComponent(impl) {
return React.createClass({
getInitialState: function getInitialState() {
return {};
},
componentDidMount: function componentDidMount() {
this.eventNode = { tagger: function() {}, parent: undefined };
this._app = _elm_lang$core$Native_Platform.initialize(
impl.init,
impl.update,
impl.subscriptions,
this.renderer
);
},
renderer: function renderer(onMessage, initialModel) {
this.eventNode.tagger = onMessage;
this.updateModel(initialModel);
return this.updateModel;
},
updateModel: function updateModel(model) {
this.setState({ model: model });
},
render: function render() {
// There won't be a model to render right away so we'll check that it
// exists before trying to call the view function
return typeof this.state.model !== 'undefined' ?
renderTree(impl.view(this.state.model), this.eventNode, 0) :
null;
}
});
}
まとめ
- elmのコードでiosアプリを動かすのはとても簡単!viewにライブラリを使ってコンパイルするだけ!
- elm-native-uiの動く理屈はちょっとソースコードを追えばわかった。ReactComponentを返す関数を作るわけだった。
elmでネイティブアプリまで作れるなんて楽しいですね!!!!