Kotlinのlet, apply, run, also

エンジニアの原です。

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でもいけるっちゃいけますからね。

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

エンジニアの原です。

昨日、「新しい技術導入に関する勉強会」にて

「ReactNativeで始めるアプリ開発」というタイトルで発表してきました。

connpass.com

スライドはSlideShareに上げています。

www.slideshare.net

今回は勉強会自体が特定の分野の発表ではないので、聞き手の知識がバラバラで

どこまでを前提知識として発表内容を考えるかが非常に難しかったです。

最終的に割とふんわりした内容になってしまって、マサカリ投げどころ満載だったのでビクビクしながら発表しました(笑)

とりあえずReact Nativeがどういうものか、導入にかんして何を考えれば良いのか、など少しでも伝わっているといいなーと思います。

今後も勉強会では積極的に発表していきたいですね。

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(...),  //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に関する発表をします。

connpass.com

ReactNativeのView構造をAndroidDeviceMonitorで確認する

こんにちは。アプリエンジニアの原です。

テックブログ始めました。

一発目はReactNativeの記事です。

巷で話題のReactNative、最近弊社のネイティブアプリをReactNative化するプロジェクトがスタートしています。

ReactNativeはロジック部分をJavascriptで、Viewの部分をJavascriptのライブラリのReactで書くことができ、実行時はReactで書いたソースからiOS/AndroidのネイティブコードのViewに変換してくれます。

では実際にReactNativeのコードからどのようなViewに変換されてるでしょうか。

ソースを読むのも1つの手ですが、AndroidSDKにはAndroidDeviceMonitorというツールがありViewツリーをGUIで確認できる機能があります。このツールを使うことでViewの構造を簡単に確認することが出来ますので今回をこれを使って見てみます。

新規プロジェクトを作成

ReactNativeとAndroidSDKの設定はできてい前提で進めていきます。

ReactNativeの環境設定はこちら facebook.github.io

AndroidStudioまたはコマンドラインツールズのダウンロードはこちら developer.android.com

react-native init で新規作成、react-native run-androidでアンドロイドのビルドが走ります。

react-native init AnectSample1
cd AnectSample1
react-native run-android

新規のプロジェクトだと下記のような画面になります。 f:id:anect:20170227233703p:plain

Javascript側のView構造は下記のようになっています。

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Text,
  View
} from 'react-native';

export default class AnectSample1 extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Text style={styles.welcome}>
          Welcome to React Native!
        </Text>
        <Text style={styles.instructions}>
          To get started, edit index.android.js
        </Text>
        <Text style={styles.instructions}>
          Double tap R on your keyboard to reload,{'\n'}
          Shake or press menu button for dev menu
        </Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
});

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

render()メソッドがViewを返します。

Viewの中にTextが3つ入っている構造です。

AndroidDeviceMonitorで確認

このViewをAndroidDeviceMonitorで確認してみます。

monitor

パスが通っていない場合はAndroid/SDK/toolsの中にあります。

AndroidDeviceMonitorが起動したら下図の赤丸の部分をおしてHierarchyViewを選択します。

f:id:anect:20170228005304p:plain

左側のナビゲーションのwindowsから作成したアプリを選択すると下記の図のようにViewの構造が表示されます。

f:id:anect:20170228005619p:plain

変換されたViewを探す

一番左がルートのViewになり階層順に右に並んでいます。 右端の一番上のReactTextViewをクリックしてみると

f:id:anect:20170228011347p:plain

となり、このViewが「Welcome to React Native」を表示していることがわかります。

同様に残り2つのReactTextViewもJavascript側で書いたテキストを表示していることがわかりました。

このことからJavascript側のTextクラスがAndroid側ではReactTextViewというViewとに変換されているということが確認できました。

またこれらを子Viewに持つReactViewGroupがJavascript側のViewクラスが変換されたものだということもわかり、Javascriptで定義したすべてのViewがネイティブのコードに変換されていることが確認できました。

まとめ

RectNativeで作成したアプリをAndroidDeviceMonitorでViewの構造を調べました。 Javascript側のViewがAndroid側のネイティブのViewとしてちゃんと変換されていることがわかりました。 実際の実装で頻繁に使うことは無いと思いますが、ReactNativeのコンポーネントがネイティブでどう変換されているかを知ることは実装やデバッグに役に立つこともあるかと思いますので機会があれば使ってみてください。