そーす

福岡在住。iOS/Androidアプリ, Webフロントエンドのエンジニアです。Swift, Kotlin, JavaScript, ReactNative

React Nativeで起動時にJS側にデータを渡す

facebook.github.io

iOSの方はちゃんとドキュメントがあるのに何でAndroidは無いんですかね…

といってもやり方は簡単です。

MainActivityでcreateReactActivityDelegateをオーバーライドして

ReactActivityDelegateのgetLaunchOptionsをオーバーライドするだけです。

public class MainActivity extends ReactActivity {

    @Override
    protected ReactActivityDelegate createReactActivityDelegate() {
        return new ReactActivityDelegate(this, getMainComponentName()) {
            @Nullable
            @Override
            protected Bundle getLaunchOptions() {
                Bundle initialProps = new Bundle();
                initialProps.putString("buildType", BuildConfig.BUILD_TYPE);
                return initialProps;
            }
        };
    }
}

熱盛りSlackBot

f:id:saburesan:20170913175357p:plain

Bitriseのビルド待っている間に熱盛り絵文字を投稿すると返信してくれるようにした。

笑ってもらえたので良かった(会社のSlack)

React Nativeのアニメーションで簡単なチュートリアル画面を作った

React Nativeで個人開発アプリを開発中なのですが、ちょうどアニメーション周りを結構調べていたので試しに簡単なチュートリアルを作ってみました。

f:id:saburesan:20170906143339g:plain

Component

ほとんどAnimatedを使いました。コンポーネントもデフォルトのAnimated.View, Text, Imageのみです。

一部LayoutAnimationを使っていますが、最初にLayoutAnimationを使っていた名残で変更していないだけです。

LayoutAnimationは導入も使い方も非常に楽なので気に入っていたのですが、複雑なアニメーションをしようとするとかなり面倒くさく、あまり自由度が無いところから使わなくなりました。

ARTを使ってSVGでやることも考えたのですが、Animatedでも十分パフォーマンスが出る結果だったので今回は採用していません。

saburesan.hatenablog.com

Easing

Easingもデフォルトのtimingとspringしか使っていません。

Sequence, Stagger, Parallel

アニメーションを連続で再生するためのAPIがありますが、今回はsequence, stagger, parallelを使いました。

facebook.github.io

ドキュメントを読んだだけだとStaggerの使い所があまりわからなかったのですが、

今回検索文字の入力の部分や、3回タップのアニメーションの所などで使っています。

Interpolate

今回、すべてのアニメーションはAnimated.Valueを使っています。

また、基本的にValueは0->1の変化のみとして、Animated.Valueの値をinterpolateメソッドで適宜補間する形にしました。

理由はValueの値をスタイルで直接使う値にしてしまうと同じタイミングで始まるアニメーションがあったときにAnimated.Valueを使いまわせないからです。

同じにタイミングでもEasingやDurationが違う場合などはParallelを使用していますが、

EasingもDurationも同じでいい場合が結構多くinterpolateで補間するほうがわかりやすくなりました。

Valueを0->1にしてinterpolateで補間することでアニメーション自体の定義とスタイルの定義を完全に別にして考える事ができるのでお薦めです。

ハマったこと

Animated.XXXにして無い時にエラーで上手く教えてくれない

これは私が初期にLayoutAnimationで組んで居た所をAnimatedに変更したときにコンポーネントの定義でAnimated.XXXにし忘れていたのですが、エラーが非常にわかりにくく手間取りました。

Devモードだとアニメーションが重すぎる

めっちゃ重たいです。デバッグ無理です。

Androidであれば開発モードのDev SettingsJS Dev Modeをオフにしてデバッグしましょう。

iOSAndroidと同じ事が出来ないのでReleaseビルドでデバッグしましょう。

position: ‘absolute'で指定しているAnimated.Viewを消すとそれ以降のアニメーションが重くなる

これはホントの所の原因がわかっていないのですが、フルスクリーン表示したViewをアニメーションが終わった後に描画しない処理(renderXXXがnullを返す)をすると、それ以降のアニメーションがやたら重くなる現象が起きました。

leftの値をいじって画面外に出しても重くなってしまい、完全な原因が謎です。

今回は一番最後にボタンをタップできるようにする必要があっただけのでフルスクリーンのViewにpointerEvents=“none”を設定することで解決できましたが、ちょっと謎ですね…

まとめ

今回やった範囲だとハマりどころが多いという感じはしませんでした。

JS側UIスレッド側のFPSも57~60位で特にパフォーマンスが悪くなるような事も特に無く、アニメーションは実際ネイティブで書くと面倒ですがAndroid/iOSどっちも同じように書けるのはかなりのメリットだなぁと思いました。

AnimatedのAPIはまだ使ってないものもありますし、ARTとAnimatedを組み合わせると更に自由度が上がると思いますので色々遊んでみたいと思います。

React Native ARTで遊ぶ

React Nativeでグラフを描くにはどうしたらいいのかと調べましたら、

React Native内にReact Native ARTというライブラリが在るのを知りました。

ドキュメントにも書いてないですが、開発はされているようです。

react-native/ReactNativeART.js at 0.48-stable · facebook/react-native · GitHub

Install

初期でiOSはライブラリの設定がされていないのでそれを行う必要があります。(RN47.0 現在)

Xcodeでプロジェクトを開き、

  1. Build Phases -> Link Binary with Libraries -> + (Add items) -> Add Other -> /node_modules/react-native/Libraries/ART/ART.xcodeproj

  2. Build Phases -> Link Binary with Libraries -> libART.a

で追加します。

これで完了です。Androidは最初から動きます

直線を描く

import React, { Component } from 'react';
import {
  AppRegistry,
  ART,
  Dimensions
} from 'react-native';

const {
  ClippingRectangle,
  Group,
  Surface,
  LinearGradient,
  Path,
  RadialGradient,
  Shape,
  Transform,
  Text
} = ART;

export default class ArtSample extends Component {
  render() {
    const { width, height } = Dimensions.get('window');
    const path = Path();
    path.moveTo(width / 2, height / 2);
    path.line(0, -100);

    return (
      <Surface width={width} height={height} >
        <Shape d={path} stroke="#AAA" strokeWidth="4" />
        <Shape d={`m${width / 2},${height / 2} v100`} stroke="#333333" strokeWidth="8" />
        <Text x={width / 2 + 30} y={height / 2} stroke="#444" strokeWidth="1">ポッキーだよ</Text>
      </Surface>
    );
  }
}

f:id:saburesan:20170831144354p:plain

ポッキーを描きました。

svgに慣れてるのであればあまり難しくなさそうですね。(pathじゃなくてShapeだけど)

ベジエ

2次ベジエ、3次ベジエ。

return (
      <Surface width={width} height={height} >
        <Shape d={path} stroke="#AAA" strokeWidth="4" />
        <Shape d={`m${width / 2},${height / 2} v100`} stroke="#333333" strokeWidth="8" />
        <Text x={width / 2 + 30} y={height / 2} stroke="#444" strokeWidth="1" font="bold 20px Futura">ポッキーだよ</Text>
        <Shape d={`m0,0 q0,${height},${width},${height} `} stroke="#777" strokeWidth="2" />
        <Shape d={`m0,0 c0,${height},${width},${height},${width},0`} stroke="#777" strokeWidth="2" />
      </Surface>
    );

f:id:saburesan:20170831151543p:plain

LayoutAnimation

LayoutAnimationも効きますね(意味のないアニメーション)

f:id:saburesan:20170831153914g:plain

ServiceWorkerのデバッグ時にはキャッシュを

ServiceWorker勉強中です。

saburesan.hatenablog.com

これやった時に通知の表記のデバッグ時に何変えても表記が上手くいかなくて悩んだんですが、

結局ServiceWorkerのキャッシュのせいでファイル更新しても上手く更新されなかったようです。

Chromeデバッグする際には

http://chrome://serviceworker-internals/

から対象のServiceWorkerをunregisterすると最新のファイルが読み込まれました。

Firebase Cloud Messagingを使ってWebサイトでPush通知を受け取る

仕事でReact SPAにPush通知を実装することになったので備忘録。

Firebase Cloud Messaging(以下FCM)をWebにやるにはHttpsに対応のサイトじゃないとだめなので、Firebase Hostingで構築するところまでを記載します。

Firebaseの説明はありません。

Firebase Hostingの準備

先にFirebase Hostingを使ってサイトを作る準備をします。

Firebaseのプロジェクトを作成する

Firebaseのアカウントを持ってないならば作りましょう。

firebase.google.com

プロジェクトを作ります。

f:id:saburesan:20170817224342p:plain

Command Line Toolsのインストール

github.com

Firebaseをコマンドで扱うためのツールです。Firebase Hostingを使う時に使用します。

npm i -g firebase-tools

インストールが完了したらログインします。

firebase login

ブラウザが開きますので連携を許可しましょう。

Firebase Hostingのプロジェクトの初期化

適当に今回プロジェクトを作成するディレクトリを作成します。

そこで以下のコマンドで初期化します

firebase init

Firebaseのどのサービスの初期化か聞かれますので一番したのHostingを選びましょう

f:id:saburesan:20170818134047p:plain

すると、プロジェクトの選択があるので、最初に作ったプロジェクトを選択します。

初期化は以上です。

下記の2つのファイルが作成されます。

.firebaserc
firebase.json

firebase.jsonは空のJSONオブジェクトしか入ってないと思います。

リファレンスでは

アプリを初期化する際、公開ルートとして使用するディレクトリについて確認を求められます(デフォルトは「public」)。その時点で公開ルート ディレクトリに有効な index.html ファイルがない場合、自動的にファイルが作成されます。

とありますが、私の環境(バージョン?)では聞かれなかったので手動で公開ルートを設定します。

とりあえずpublicにしましょう。

{
  "hosting": {
    "public": "public"
  }
}

あとはpublicフォルダを作ってindex.htmlを仕込んでデプロイします。

f:id:saburesan:20170818135253p:plain

ちなみに、デプロイ前の確認としてローカルサーバを建てる機能もあります。

firebase serve
firebase deploy
=== Deploying to '#####'...

i  deploying hosting
i  hosting: preparing public directory for upload...
✔  hosting: 1 files uploaded successfully
i  starting release process (may take several minutes)...

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/#####/overview
Hosting URL: https://#####.firebaseapp.com

Hosting URLをブラウザで開くとちゃんとデプロイされてるのが確認出来ます。

FCMの設定

ブラウザからプロジェクトを開くとウェブアプリにFirebaseを追加というボタンがあるので

クリックすると設定のスクリプトが書いてあるのでそれをコピペしてindex.htmlに貼り付けましょう。

通知の許可を取るまではローカルサーバでも大丈夫です。

index.html

<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>FCM Sample</title>
</head>

<body>
  <script src="https://www.gstatic.com/firebasejs/4.3.0/firebase.js"></script>
  <script>
    // Initialize Firebase
    var config = {
      apiKey: ###
      authDomain: ###,
      databaseURL: ###,
      projectId: ###
      storageBucket: ###,
      messagingSenderId: ###,
    };
    firebase.initializeApp(config);
  </script>
</body>

</html>

これでプロジェクトの初期設定が完了です。

ServiceWorkerの作成

firebase-messaging-sw.js

FCMはルートディレクトリにあるfirebase-messaging-sw.jsというファイルをServiceWorkerに読み込ませます。

なので、このファイルをpublic以下に作成して中身を以下にします。

importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.9.0/firebase-messaging.js');

firebase.initializeApp({
  messagingSenderId: "###", // index.htmlにあるconfigのmessagingSenderIdと同じもの
});

const messaging = firebase.messaging();

manifest.json

manifest.jsonもpublic以下に作成します。manifest.jsonの詳しい説明はこちら

ウェブアプリ マニフェスト  |  Web  |  Google Developers

とりあえず今回は中身を以下にします。

{
  "gcm_sender_id": "103953800507"
}

gcm_sender_idは固定値で、全ての環境で同じ値を使います。

通知の許可を取る

次にユーザに通知を受け取るかどうかの許可を取ります。

    firebase.initializeApp(config);

    const messaging = firebase.messaging();
    messaging.requestPermission()
      .then(() => console.log('granted'))
      .catch(e => console.log(e));

これで実行すると通知の許可を取るダイアログが表示されます。

f:id:saburesan:20170818164215p:plain

InstanceIDトークンの取得

InstanceIDとはFirebaseのプロジェクトごとにユーザに割り当てられるIDのことです(多分)

それを元にしたトークンを使って個人ユーザにメッセージを送信することができます。

   messaging.requestPermission()
      .then(() => messaging.getToken())
      .then((token) => console.log(token))
      .catch(e => console.log(e));

Push通知はサーバから送ると思うのでこの時点でtokenをサーバに送りましょう。

メッセージの受信設定

フォアグラウンド通知(サイトを開いている状態)を受け取るためには以下の方法で受け取れます。

    messaging.requestPermission()
      .then(() => messaging.getToken())
      .then((token) => {
        console.log(token);
        messaging.onMessage(payload => alert(JSON.stringify(payload)));
      })
      .catch(e => console.log(e));

Push通知を送ってみる

ではPush通知を送ってみましょう。

送るのに必要なのはFirebaseプロジェクトのサーバーキーとInstanceIDトークンです。

https://fcm.googleapis.com/fcm/send
Content-Type: application/json
Authorization: key="Firebaseプロジェクトのサーバーキー"

{ "notification": {
    "title": "Hello",
    "body": "body",
    "click_action" : "http://saburesan.hatenablog.com/"
  },

  "to" : "// InstanceIDトークン"
}

フォアグラウンド通知(サイトを開いている状態)

f:id:saburesan:20170818172817p:plain

バックグラウンド通知(サイトが開かれていない状態)

f:id:saburesan:20170818173244p:plain

通知のpayloadを

"notification : {
   "title: " string, // 通知のタイトル
   "body": string, // 通知の本文
   "click_action": string, // タップした時に開きたいリンク
   "icon": string // 通知のアイコンに設定したい画像のパス
}

の形式にしておくと上記のスクリーンショットのように勝手に設定されます。

これでPush通知の基本的な実装は終わりです。

Topicsの購読

Firebaseには複数のユーザに同時にPushを送るTopicsという機能があります。

そのTopicsを購読するには以下のようにしてIID_TOKEN, TOPIC_NAME, PROJECT_SERVER_KEYを用いてリクエストを送る必要ことで購読することが出来ます。

fetch(`https://iid.googleapis.com/iid/v1/IID_TOKEN/rel/topics/TOPIC_NAME`, {
    method: 'POST',
    headers: {
      Authorization: PROJECT_SERVER_KEY,
    },
  }),

まとめ

纏まってない。

React Native version 0.47.0 is out

github.com

Breaking changes

Android

Remove unused createJSModules calls (ce6fb33, 53d5504) - @javache

ReactPackageというinterfaceからcreateJSModulesが削除されました。

これ結構ヤバイ変更だと思っています。

Androidのネイティブにアクセスするライブラリは軒並みこの変更に対応しないとビルド通りません。

ほぼすべてのライブラリで変更が必要なんじゃないかな

前バージョンではDeprecatedですらなかったのにこの変更は辛い。

(タグはv0.48.0-rcなんだけどな…)

自前のブリッジモジュールもそうですが、ライブラリの対応に追われますね…

Kotlinのrunとalsoの使い所

f:id:saburesan:20160906091652j:plain

twitterのタイムラインに

「runとalsoの使い所分からん」

というツイートが流れてきたので。

Kotlinには便利な拡張関数があるのですが、それぞれが微妙に違うので用途を結構迷います。

そこでrun, alsoに加えよく使うであろうletとapplyの4つの特徴から用途を考えていこうと思います

定義の確認

run

public inline fun <T, R> T.run(block: T.() -> R): R = block()
  • レシーバの拡張関数
  • 任意の型を返す
  • thisはレシーバ

let

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
  • レシーバの拡張関数
  • 任意の型を返す
  • スコープ内外でthisが同じ

apply

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
  • レシーバの拡張関数
  • 返り値はレシーバ
  • thisはレシーバ

also

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }
  • レシーバの拡張関数
  • 返り値はthis
  • スコープ内外でthisが同じ

違い

返り値がレシーバ(apply, also)

apply, alsoは返り値がレシーバに固定されます。 なのでレシーバの内部状態を変えるような処理(初期化など)が想定されます。

返り値が任意(run, let)

run, letは返り値がスコープ内の最後の処理の型になります。 つまり、レシーバを加工したい場合に使う事ができます。 加工はapply, alsoでは出来ない処理です。

スコープ内thisがレシーバ(run, apply)

レシーバ内のthisがレシーバに固定されます。 レシーバのメソッドを呼んだり、レシーバに対する処理を行うときは使いやすい。 反面、スコープ外のthisに参照したいときはthis@~~と各必要がある。

スコープ内外でthisが同じ(let, also)

tスコープ内のthisがスコープ外と同じです。thisが変わらないのでthis@~~と書く必要が無いので楽。 反面、レシーバはitなどで受け取らないといけない。 スコープ内でthisを多用するときは良いかも。

用途

上記を踏まえて私が思った使いどころ

let

ある値の加工に使う

val age = 10
val ageStr = age.let{ "age : $it" }

//Nullable時とかよく見るよね
age?.let { " age: $it" }

letはitでレシーバにthisでスコープ外にアクセスできて任意の型を返せるので、

レシーバの加工には一番適していると思います。

apply

初期化処理、メソッドの複数呼び出し

val human = Human().apply { 
   name = "sabure"
   age = "30"
   context = this@HogeActivity
}

applyはthisがレシーバでレシーバを返すので内部状態の変更・初期化処理などに向いているかと思います。

あとはthis@~が複数在るような場合はalsoだと混乱しそうなのでapplyが良いかと思います。

run

エルビス演算時のnullの時の処理?

val name = n ?: run {
   ...
   ...
}

KotlinのNullチェックはエルビス演算子を使うと思うのですが、

nullの時に幾つか処理をして返り値が必要な時などに使えるのではないでしょうか。

also

applyで処理するにはthisの参照が多そうなときかな…

val human = Human().also { 
   setOnClickA { this.startActivity(...) }
   setOnClickB { this.startActivity(...) }
   setOnClickC { this.startActivity(...) }
}

うーん、alsoはあんまり良い使い方思いつきませんね…

返り値がレシーバ固定なのでレシーバに関係ない事をするあまり良くないですし、

そう考えるとthisもレシーバのapplyの方がalsoに比べると使う機会が多そうな気がします。

まとめ

Kotlinの拡張関数は基本的に使ってて気持ちいいくらいなんですが、

それぞれの特徴を踏まえて用途を考えて使わないと、メンバー内などで書き方の統一が難しくなるので気をつけないといけませんね。

全部letでもいけるっちゃいけますからね。

「新しい技術の導入に関する勉強会」で発表してきました。

www.slideshare.net

SlideShareにアップロードしてます。

Keynoteで作ったのをPowerPointに変換して上げてるので若干レイアウト崩れてます(修正する元気無い)

React Nativeに関する簡単な紹介です。

福岡ではあまりReactやReact Nativeの話聞かないので少しでも知って貰えればなーという感じで作ったのですが、

結構Web,ネイティブアプリエンジニアじゃない方が多くて

「ReactとReact Nativeって違うんですか?」

「Viewって何?」

みたいな人もいたっぽいので若干内容ミスってました。

スライドも結構お粗末な感じになっちゃいました…

30分発表とか修論発表以来だわーー…反省

結構細かい所を濁しての発表で、マサカリチャンス結構あったのでビクビクしてました(笑)(ReactJSの説明のところとか)

どこまでの前提知識でスライド作るかって結構難しいですね。

発表は基本的に辛い話をしたほうが良い

私の発表を聞いた人は多分、誰かに「React Nativeってどんな感じだった?」って聞かれたら

「なんか辛そう…」

って言う人が居ると思います。

というかそういうふうに思ってもらうために発表の最後に辛い話をして、辛い話が記憶に残るようにしています。

新しめのフレームワークはやはり、新しいだけあって流行りの技術やニーズに対応した機能などが豊富です。

しかし、そのような良い特徴は公式がまず絶対紹介しています。

そりゃ使ってほしいから特徴や導入メリットなどはちゃんと書いてくれますよね。

しかし、公式は辛い話は書きません。

そりゃ使ってほしいからあえて辛い話とか書きませんよね。

私はその辛い話というのか一番の「知見」だと思っています。

辛い話は使ってみないとわからないです。

仮に、私の発表を聞いた人がReact Nativeの導入を考えた時に

公式が紹介する良い所と、

私が伝えた辛い所

両方を考慮して導入を検討してくれることを期待しています。

何はともあれ発表たのしい!またやりたい。

AndroidのカスタムOSが使われている端末でDevメニューが出ない時の対処

もー、クソん。

Oppo。

KeyEvent.KEYCODE_MENU(82番) がシステム側で受け取られててアプリまで来ない。

ちなみにOppoはシェイクしてもDevモード出ない。

しかたなく他の空いているキーコードをアプリで受け取って書き換えることに…

処理は簡単で、ReactActivity内でonKeyUp(int keyCode, KeyEvent event)をオーバーライドするだけ。

とりあえず300番代は空いていたので300を設定してみた。

public class MainActivity extends ReactActivity {

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "MyApp";
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        MainApplication.getCallbackManager().onActivityResult(requestCode, resultCode, data);
    }

    // 追加
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        return super.onKeyUp(keyCode == 300 ? KeyEvent.KEYCODE_MENU : keyCode, event);
    }
}
> adb shell input keyevent 300

これでデバッグできる。

react-reduxのmapStateToPropsとmapDispatchToPropsは必要なのか

ReactNativeでアプリを作る時はReduxを使っていて、ReduxのStoreとReactのComponentをBindingしてくれるreact-reduxが便利なので使っています。

github.com

react-reduxが提供するconnectというStoreとComponentをBindingする関数が在るのですが、

connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
)(Component)

という感じで、3つの関数を引き数にしています。

mapStateToProps

これはReduxのStoreを第一引き数に取る関数で、ComponentにPropsとして渡すものをフィルタリングしたい時に使います。例えばStoreのUserからnameをPropsとして渡したい場合は

//第一引き数はstore
const mapStateToProps = store => ({ name: store.user.name });

connect(mapStateToProps)(Component);

//Componentはthis.props.nameでアクセス可能

こんな感じでフィルタリングしてComponentにPropsと渡すことが出来ます。

mapDispatchToProps

これはReduxのDispatchを第一引き数に取る関数で、変更を伝えるアクションを作成する時に使います。例えばボタンが押された時にUserのnameを変更するアクションを作成する場合は

// 第一引き数はReduxのdispatch関数
const mapDispatchToProps = dispatch => ({ updateName: name => dispatch({ type: UPDATE_NAME, name }) });

connect(null, mapDispatchToProps)(Component);

//Componentはthis.props.updateName('name')でアクセス可能

こんな感じで作成したアクションをComponentのPropsとして渡すことが出来ます。

mergeProps

これは第一引き数にmapStateToProps、第二引き数にmapDispatchToPropsをとる関数で新しPropsを作成します。 デフォルトでは

Object.assign({}, ownProps, stateProps, dispatchProps)

という処理を行います。

(ownPropsはComponentが持つプロパティです。mapStateToProps, mapDispatchToProps共に第二引き数, mergePropsは第三引き数で受取ることができます)

疑問

結論からいうとmapStateToPropsとmapDispatchToPropsの使い所がわかりません

例えば, Userの情報をサーバーから取得してStoreを更新する処理を考えます。

ユーザーの情報を取得するAPIにはユーザーのトークンが必要なのでStoreから取得する必要があります。

サーバーから取得したレスポンスでStoreを更新するためにDispatch関数が必要です。

なので、mergePropsを使わない場合は

const mapStateToProps = store => ({ token: store.user.token });

const mapDispatchToProps = dispatch => ({ fetchUser: token => fetch(...) }  });

connect(
  mapStateToProps,
  mapDispatchToProps,
)(Component);

のようになり、ComponentがfetchUserにトークンを渡す責務が発生します。

Componentは表示やUIイベントの通知に関わる処理だけをするべきであり、またtoken自体描画には全く関係無いのでPropsとして渡すのは不適切な気がします。

なので、ここでmergePropsを使ってtokenを渡す処理を内包すると

const mapStateToProps = store => ({ 
  token: store.user.token 
});

const mapDispatchToProps = dispatch => ({ 
  fetchUser: token => fetch(...), 
});

const mergeProps = (state, action) => ({
     fetchUser: () => action.fetchUser(state.token),
});

connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps
)(Component);

これでも可能ですが、

なんなら

const mapStateToProps = store => store;

const mapDispatchToProps = dispatch => ({ dispatch });

const mergeProps = (store, { dispatch }) => ({
    fetchUser: () => {
        const token = store.user.token;
        fetch(...);
    }
});

connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps
)(Component);

コレが一番シンプルなのでは??

という結論に今のところ至っており、

mapStateToPropsとmapDispatchToPropsは必要なのか

です。

だれか教えてください。よろしくお願いします!

「新しい技術導入に関する勉強会」に登壇します。

宣伝です。

「ReactNativeで始めるアプリ開発(仮)」という内容で登壇する予定です。

ReactNativeを触ったことがないWebエンジニア、アプリをエンジニアにわかりやすくメリット・デメリットを話せればと思います。

ReactNativeでアプリの開発環境(エディタ, ESLint, Flow編)

私がReactNativeを使ってアプリを開発するときの環境や初期設定です。

Visual Studio Code

Visual Studio Code - Visual Studio

1ヶ月前位にAtomから乗り換えました。

最初に変えた理由は何となくエディタを変えたかったので(笑)

とりあえず使ってみるとすごく快適に使えました。

Atomから乗り換えて一番よかったのは、エディタの設定です。

f:id:saburesan:20170602093805p:plain

atomはインストールしたパッケージの設定変更や、エディタ自体の設定変更が面倒(だと個人てきには思っていて)。

VSCodeは上記設定画面からエディタの設定とインストールしたパッケージの設定が行えます。

また、設定の変更が容易でわかりやすいです。

画面右がデフォルトから変更したユーザ個人の設定で、左が現在変更可能な設定一覧です。

例えば、よく使用するものfiles.autoSaveを変更したいとします。

f:id:saburesan:20170602093454p:plain

デフォルトではoffになっています。

他には何が設定できるかはfile.autoSaveはコメントに書いてあるのですが、

変更はカーソルを上にもってきた時に左に表示されるエンピツアイコンをクリックすると

f:id:saburesan:20170602094946p:plain

画像のように設定一覧だしてくれて選択出来ます。

これはめっちゃいいなーと思いました。

ESLint

eslint.org

ESLintはJavascriptの構文チェックツールです。

設定したコーディング規約に準拠していない場合はエラーを出してくれます。

Fileのパスの検証などもやってくれるので実行前に検出してくれます。

インストー

規約は自分で作るとかなり面倒なので、Airbnbが公開しているeslint-config-airbnbというのを使っています。

github.com

パッケージをインストールして

export PKG=eslint-config-airbnb;
npm info "$PKG@latest" peerDependencies --json | command sed 's/[\{\},]//g ; s/: /@/g' | xargs npm install --save-dev "$PKG@latest"

.eslintrcを作成して

{
  "extends": "airbnb"
}

を追記します。

エディタにPluginをインストー

次にエディタにeslintのプラグインをインストールします。

VSCodeでプラグインをインストールするには⌘+Ctrl+XでVisualStudioCodeのプラグインマーケットプレイスから検索ができます。

ESLintと打つと一番上に出てくるプラグインをインストールします。

f:id:saburesan:20170602104426p:plain

innsu

そうするとESLintのエラーをエディタ上で確認することが出来ます。

f:id:saburesan:20170602105129p:plain

エディタ上の赤い波線がESLintのエラー箇所になります。

画面下部のエラー一覧の画面は⌘+Ctrl+Mで出ます。

あとはエラー文言にそって修正していくだけです。

ReactNative用の設定を追記

eslint-config-airbnbにはReactNativeの設定が無いため追記します。

ReactNativeではXXX.ios.jsとXXX.android.jsをimport XXX from './XXX';という形式でインポートできるのですが、これはESLint上だとエラーになるので、.eslintrcにエラーが出ないように追記します。

{
  "extends": "airbnb",

  "settings": {
    "import/resolver": {
      "node": {
        "extensions": [
          ".js",
          ".ios.js",
          ".android.js"
        ]
      }
    }
  },
  "env": {
    "node": true
  }
}

settingsが追記分です。これでXXX.ios.jsのインポートエラーが解消されます。

Flow

flow.org

Flow_はFacebookが開発しているJavascript用の静的型チェックツールです。

プラグインのインストー

ReactNativeはデフォルトでFlowの設定がされているので、エディタのプラグインのインストールとflow-binのインストールだけで大丈夫です。

プラグインはESLintと同じように、

マーケットプレイスからFlow Language Supportをインストールします。

Flow Language Supportがグローバルにインストールされているflow-binを必要としますので、

npm i -g flow-bin

でインストールします。

これでVSCode上にもFlowのエラーが表示されるはずです。

エラー対処

おそらく、node_modulesの中もチェックされて大量にエラーが出てるかと思います…

そこで.flowconfignode_modulesをチェックしないように追記します。

[ignore]
.
.
.

.*/node_modules/.*

これでnode_modules以下のエラーは消えますが、react-nativeモジュールが見つからないエラーが出ます。

これはreact-nativeというモジュールをFlowが認識できるように定義することで対応できます。

.flowconfig[libs]というのがあるのですが、この[libs]に指定されているフォルダに定義を宣言することでFlowはその定義を元にチェックを行います。

React-Nativeのデフォルトでは

node_modules/react-native/Libraries/react-native/react-native-interface.js
node_modules/react-native/flow
flow/

上記のようになっているのでflowフォルダを作成して以下の内容のファイルを作成します。

declare module 'react-native' {
  declare module.exports: any;
}

この方法だとエラーはでなくなるのですが、これだと新しいモジュールの度に追加する必要があり、コードジャンプや補完が効かなくなります…

他の解決方法があれば知りたい…

おそらくmoduleでESLintのエラーがでるので、さらに.eslintrc

  "plugins": [
    "flowtype"
  ]

を追記します。

これでFlowの設定は一旦終わりです。

あとはflow/に定義ファイルを入れるとプロジェクト全体で定義のFlowチェックが効きます。

定義

// flow/Types.js
declare type Human = {
  name: string,
  age: number,
};

Twitter LiteはReactNativeWebで作られてる???

https://mobile.twitter.com/home

ちょっと前にTwitterLiteがリリースされました。

なんでもProgressive Web App(PWA)対応したとかで話題になってました。

はじめてのプログレッシブ ウェブアプリ  |  Web  |  Google Developers

気になってソースを少し見てみたのですが、なんだかReactNativeWebを使っているのでは??というソースを少し見かけました。

f:id:saburesan:20170531061527p:plain

写真のreact-native-xxxってやつですね。

JSのソース内にもreact_native_webという文字も有りました。

React Native Webとは

github.com

ReactNativeのソースをWeb用にも変換できるライブラリです。

まだ使ったことはないです。

んで、公式のサンプルを見てみるとスタイルの吐き出し方がTwitterLiteと同じでした。

公式サンプル https://www.webpackbin.com/bins/-KlQDQP2QfUqxXRQA8uc

なのでそのことに言及されてる記事とか無いかなと思ってしらべたら、

ReactNativeWebのオーナーが

「I’ll be sharing more about Twitter Lite (and React Native for Web) at React Europe on May 19th」

という発言をTwitterでしています。

このツイートは削除されていて、キャッシュが残っていたのでみれたのですが何か不都合があったかもしれないのでスクショは載せないでおきます。

調べるまで知らなかったのですが、ReactNativeWebのオーナーはTwitterの社員でTwitterLiteのTechnical Leaderだそうです。

うーん、可能性としてはありそうですよね…