ReactNativeの開発環境をサクッと構築

アプリを作ってみたくなり、Web開発者としてとっつきやすそうなReactNativeに入門してみました。 この記事の内容は、開発環境構築から実機で動作確認するところまでです。

環境

  • macOS Sierra
  • Node.js v9.2.1
  • Android / iOS の実機 expoアプリをインストールしておきます

今回は試してませんが実機の代わりにシミュレータでも動作します。

XDEをインストール

以下のリンクからExpo SDKをDLしてインストールします。 起動するとアカウント登録を促されるので登録します。

CRNAをインストール

面倒な環境構築をすっ飛ばしてくれるすごいやつ。 好みでyarnを使ってインストールしていますが、もちろんnpmでもできます。

$ yarn global add create-react-native-app

yarn global v1.3.2
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
[4/4] 📃  Building fresh packages...
success Installed "create-react-native-app@1.0.0" with binaries:
      - create-react-native-app
✨  Done in 28.44s.

インストールしたら早速アプリを作成しましょう。 名前は何でもいいのでtest-appとしました。

$ create-react-native-app test-app

Creating a new React Native app in /Users/yuto/practice/react-native/test-app.

Using package manager as yarnpkg with yarn interface.
Installing packages. This might take a couple minutes.
Installing react-native-scripts...

...

Success! Created test-app at /Users/yuto/practice/react-native/test-app
Inside that directory, you can run several commands:

  yarn start
    Starts the development server so you can open your app in the Expo
    app on your phone.

  yarn run ios
    (Mac only, requires Xcode)
    Starts the development server and loads your app in an iOS simulator.

  yarn run android
    (Requires Android build tools)
    Starts the development server and loads your app on a connected Android
    device or emulator.

  yarn test
    Starts the test runner.

  yarn run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd test-app
  yarn start

Happy hacking!

作成されたディレクトリに移動し、開発サーバーを起動します。

$ cd test-app/
$ yarn start

See https://git.io/v5vcn for more information, either install watchman or run the following snippet:
  sudo sysctl -w kern.maxfiles=5242880
  sudo sysctl -w kern.maxfilesperproc=524288

watchman を入れてねと言われたのでインストールします。 watchmanファイルシステムの監視ツールです。

$ brew install watchman

気を取り直して再度起動します。

$ yarn start

うまくいくとビルドがはじまり、ターミナルにQRコードが表示されます。 iOSAndroidの実機を用意し、expoアプリから上記のコードを読み込むと、実機で動作確認することができます。

f:id:kinakobo:20171225004317p:plain:w200

コードの変更もすぐに反映されます。お手軽すごい。

ついでにコンポーネントライブラリを入れて遊んでみます。 NativeBaseをインストール

Getting Started · NativeBase

$ yarn add native-base
$ yarn add @expo/vector-icons

App.jsを以下のように変更しました。 NativeBaseにはカスタムフォントが含まれるため、コンポーネントレンダリングする前にフォントをロードするようにしました。 他にはrender内に公式のサンプルコンポーネントのコードを適当に追加しています。

import React from 'react';
import { Container, Header, Content, Footer, FooterTab, Button, Icon, Text } from 'native-base';
import Expo from 'expo';

export default class App extends React.Component {
  state = { fontLoaded: false };

  async componentWillMount() {
    await Expo.Font.loadAsync({
      'Roboto': require('native-base/Fonts/Roboto.ttf'),
      'Roboto_medium': require('native-base/Fonts/Roboto_medium.ttf'),
    });

    this.setState({fontLoaded: true});
  }

  render() {
    return (
      <Container>
        <Header />
        <Content />
        {
          this.state.fontLoaded ? (
            <Footer>
              <FooterTab>
                <Button vertical>
                  <Icon name="apps" />
                  <Text>Apps</Text>
                </Button>
                <Button vertical>
                  <Icon name="camera" />
                  <Text>Camera</Text>
                </Button>
                <Button vertical active>
                  <Icon active name="navigate" />
                  <Text>Navigate</Text>
                </Button>
                <Button vertical>
                  <Icon name="person" />
                  <Text>Contact</Text>
                </Button>
              </FooterTab>
            </Footer>
          ) : null
        }
      </Container>
    );
  }
}

実機で確認 👀

iOS Android
f:id:kinakobo:20171225003249p:plain:w200 f:id:kinakobo:20171225003258p:plain:w200

いい感じにフッターが iOS / Android のUIで表示されました。

【Scala】型パラメータを指定せずに呼び出せるメソッド

Option型に対して中身があるかどうかを判定するメソッドを書いている時に、Optionの中身の型は気にしないで判定する共通のメソッドがほしくなりました。

以下のように型パラメータを指定してあげれば、メソッドを呼ぶ時に型を指定しなくても型推論が効いてくれて動作しました。

scala> def require[T](param: Option[T]): T = {
     |   param match {
     |     case Some(x) => x
     |     case None    => throw new Exception
     |   }
     | }
require: [T](param: Option[T])T

メソッドを呼び出してみる

scala> val hoge: Option[Int] = Option(123)
hoge: Option[Int] = Some(123)

scala> require(hoge)
res0: Int = 123

scala> val fuga: Option[String] = Option("aaa")
fuga: Option[String] = Some(aaa)

scala> require(fuga)
res1: String = aaa

scala> val none: Option[String] = Option(null)
none: Option[String] = None

scala> require(none)
java.lang.Exception
  at .require(<console>:14)
  ... 29 elided

ScalikeJDBCでページネーションに必要なクエリを発行する

ScalikeJDBCを使ってページネーションするにはどうすればよいか調べました。 DBはMySQLを使用します。

MySQLで発行するクエリ

SELECT SQL_CALC_FOUND_ROWS * -- LIMIT を付けなかった場合に返されるはずの行数を知るためのオプション
FROM users
LIMIT 10 -- 取得する行数を制限する
OFFSET 5; -- 開始位置(行数)

SELECT FOUND_ROWS(); -- 直前に実行したクエリの行数を取得する

ポイント:FOUND_ROWS()

直前に実行したクエリの行数を取得する 直前に実行したクエリにSQL_CALC_FOUND_ROWSが指定されていた場合、「LIMIT を付けなかった場合に返されるはずの行数」を取得できる

ScalikeJDBCで書く

DSLを使って書くとこんな感じ

withSQL {
  select(sqls"sql_calc_found_rows *")
    .from(User as u)
    .limit(10)
    .offset(5)
}.map(User(u)).single.apply()

SQL("select found_rows() as total").map(_.long("total")).single.apply()

ハマったところ

メソッド内で上のコードを実行すると、SQL操作ごとに新しいセッションを開始するためか、select found_rows()のクエリで直前のクエリを認識できない(totalCountが1件になる)

def totalCount(implicit session: DBSession = AutoSession): Option[Long] = {
  withSQL...
}

res: Option[Long] = Some(1) // 1件になってしまう

解決策:DB.readOnly で囲えば正しい結果を取得できた

def totalCount: Option[Long] = {
  DB readOnly { implicit s =>
    withSQL...
  }
}

res: Option[Long] = Some(3) // 正しい結果がとれる

--- 12/25 追記 ---

Call back function after list.apply · Issue #63 · scalikejdbc/scalikejdbc · GitHub

AutoSession starts new session for each SQL operation

ScalikeJDBCの AutoSessionSQL操作ごとに新しいセッションを開始する仕様とのことなので、 ReadOnlySession を使ってクエリ実行後に session.close する方法なら、DB.readOnly で囲わなくても良さそうです。

Scala学習【エラー処理に使う型】

前回に引き続きScalaの学習。今回はエラー処理で使うであろう戻り値をラップする型について。

Option型

  • 値をひとつだけ入れることができる
  • 2種類の形を持っている
    • Some(x) 値があるときの形
    • None 値がないときの形
  • 値があったりなかったりする値を表現するときに使う
  • エラー内容を表現できないので、エラー処理では使わない
// Some: 値があるとき
scala> val some: Option[String] = Option("hoge")
some: Option[String] = Some(hoge)

scala> some.get
res7: String = hoge

// None: 値がないとき
scala> val none: Option[String] = Option(null)
none: Option[String] = None

scala> none.get
java.util.NoSuchElementException: None.get
  at scala.None$.get(Option.scala:349)
  at scala.None$.get(Option.scala:347)
  ... 29 elided

Noneget したときの例外は getOrElse メソッドを使えば回避できる

scala> option.getOrElse("値がありません")
res3: String = 値がありません

パターンマッチを使って処理することもできる

scala> val some: Option[String] = Option("hoge")
some: Option[String] = Some(hoge)

scala> some match {
     |   case Some(str) => println(str) // Someの中身をstrという変数に束縛している
     |   case None => print("None")
     | }
hoge

Optionはコレクションの性質を持っているので、例えばmapを使える

// Some の場合
scala> Some(3).map(_ * 3)
res10: Option[Int] = Some(9)


// None の場合は関数を適用しても None のまま
scala> val none: Option[Int] = None
none: Option[Int] = None

scala> none.map(_ * 3)
res11: Option[Int] = None

// foldメソッドを使えばデフォルト値を指定できる
scala> none.fold(0)(_ * 3)
res12: Int = 0

Either型

  • Option型と違い、エラー時に任意のエラーの種類まで取得できる
  • RightとLeftの2つの値を持つ
    • Right: 正常な値
    • Left: エラー値
scala> val right: Either[String, Int] = Right(123)
right: Either[String,Int] = Right(123)

scala> val left: Either[String, Int] = Left("abc")
left: Either[String,Int] = Left(abc)

// パターンマッチもできる
scala> right match {
     |    case Right(i) => println(i)
     |    case Left(s)  => println(s)
     | }
123

Option型と同様にmapメソッドを持っている Either型の左右が平等に扱われる場合、mapメソッドはどちらに適用されるのだろう。となるが、暗黙的にrightを優先される(Scala2.12から) Scala2.12より前は平等に扱われるため、RightProjection型に変換する必要があった

// サジェストされるメソッドを確認
scala> right.
canEqual   filterOrElse   forall      isLeft     joinRight   merge            productIterator   swap       toTry
contains   flatMap        foreach     isRight    left        productArity     productPrefix     toOption
exists     fold           getOrElse   joinLeft   map         productElement   right             toSeq

// Scala2.12
scala> right.map(_ * 3)
res0: scala.util.Either[String,Int] = Right(369)

// Scala2.12より前
scala> right.right
res1: scala.util.Either.RightProjection[String,Int] = RightProjection(Right(123))

scala> right.right.map(_ * 3)
res0: scala.util.Either[String,Int] = Right(369)

Try型

  • 2つの値をとる
    • Success: 正常な値
    • Failure: エラー値(ただしThrowableというExceptionスーパークラスしか入れられない)
  • applyで生成する際に例外をキャッチし、Failureにする
  • NonFatal(致命的でない)という種類の例外だけキャッチする
scala> import scala.util.Try
import scala.util.Try

scala> val failure: Try[Int] = Try(throw new RuntimeException("to be caught"))
failure: scala.util.Try[Int] = Failure(java.lang.RuntimeException: to be caught)

scala> val success = Try(3)
success: scala.util.Try[Int] = Success(3)

参考

Scalaスケーラブルプログラミング第3版 - インプレスブックス

N予備校 プログラミングコース

Scala学習【コレクション】

最近Scalaをはじめたので、学習した内容をブログに書いていけば、知識が補強されて理解が深まるのでは?と思い書いていきます。 今回はScalaの豊富なコレクションライブラリーについて簡単に特徴をまとめました。おそらく基本的なものしか網羅できてません。

シーケンス

Seq型はただのトレイトであり、実際の実装は複数存在している

List型

  • 先頭の値(head)とその後ろのリスト(tail)により構成される
  • 不変
  • 先頭の値の取得や削除は高速だが、任意の添字を持つ要素へのアクセスは、リストを線形に辿っていくため、高速ではない
  • 先頭の値の取得や削除は高速なので、パターンマッチに適している
scala> List(1, 2, 3)
res0: List[Int] = List(1, 2, 3)

scala> 3::res0
res1: List[Int] = List(3, 1, 2, 3)

Array型

  • Javaの配列と互換性を持った型
  • 可変
  • Javaの配列は、宣言時にメモリの確保を行う。
  • そして、添字で数値を指定してそのメモリ上の値を取得するため、添字を使った特定の要素の参照は、一度のアクセスで行うことができ高速
  • 一方挿入削除を行う際は、再度配列のメモリの確保を行うのでメモリ効率は悪くなる
scala> Array(1, 2, 3)
res3: Array[Int] = Array(1, 2, 3)

scala> res3(1)
res4: Int = 2

ListBuffer

  • 可変
  • 要素を先頭に挿入するときも末尾に追加するときも一定時間で処理できるリスト
scala> import scala.collection.mutable.ListBuffer
import scala.collection.mutable.ListBuffer

scala> val buf = new ListBuffer[Int]
buf: scala.collection.mutable.ListBuffer[Int] = ListBuffer()

scala> buf += 1
res6: buf.type = ListBuffer(1)

scala> buf += 2
res7: buf.type = ListBuffer(1, 2)

scala> 3 +=: buf
res8: buf.type = ListBuffer(3, 1, 2)

ArrayBuffer

  • 可変
  • 配列の先頭と末尾で要素を追加・削除できる
scala> import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer

scala> val buf = new ArrayBuffer[Int]()
buf: scala.collection.mutable.ArrayBuffer[Int] = ArrayBuffer()

scala> buf += 12
res9: buf.type = ArrayBuffer(12)

scala> buf += 15
res10: buf.type = ArrayBuffer(12, 15)

scala> 3 +=: buf
res11: buf.type = ArrayBuffer(3, 12, 15)

Range

  • 範囲を表すオブジェクト
scala> 1 to 3
res12: scala.collection.immutable.Range.Inclusive = Range 1 to 3

scala> 1 to 17 by 4
res14: scala.collection.immutable.Range = Range 1 to 17 by 4

scala> 1 until 3

文字列(StringOps)

  • Predef(Scalaソースファイルに暗黙のうちにimportされているオブジェクト)がStringからStringOpsへ暗黙の型変換を提供しているため、すべての文字列はシーケンスのように扱うことが出来る。
scala> val s = "hoge"
s: String = hoge

scala> s(1)
res5: Char = o

集合

Set

  • 要素の集合を表す型
  • 要素は重複しない
  • 可変と不変があり、デフォルトは不変
  • 順番を維持しない(順序が必要な際はTreeSetを用いる)
scala> val s = Set(1, 1, 2, 2, 3, 3)
s: scala.collection.immutable.Set[Int] = Set(1, 2, 3)

連想配列

Map

  • キーと値を持つ連想配列
  • 可変と不変があり、デフォルトは不変
  • キーと値のセットは2要素のタプルで表現されている
  • 添字を使って中身の値を取得することができるが、nullが発生する可能性があるため、基本はgetメソッドを利用してOptionの値を取得する
  • 順番を維持しない(順序が必要な際はTreeMapを用いる)
scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3)
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3)

コレクション

Stack

  • 後入れ先出しのコレクション
  • 可変と不変がある
  • pushメソッドで要素を入れ、pop2メソッドで先頭の値と残った要素を持つStackをタプルとして取得できる
scala> scala.collection.immutable.Stack()
res0: scala.collection.immutable.Stack[Nothing] = Stack()

scala> res0.push(1)
res1: scala.collection.immutable.Stack[Int] = Stack(1)

scala> res1.push(2)
res2: scala.collection.immutable.Stack[Int] = Stack(2, 1)

scala> res2.pop2
res3: (Int, scala.collection.immutable.Stack[Int]) = (2,Stack(1))

Queue

  • 先入れ先出しのコレクション
  • 可変と不変がある
  • enqueueメソッドで要素を入れ、dequeueメソッドで先頭の値と残った要素を持つQueueをタプルとして取得できる
scala> scala.collection.immutable.Queue()
res4: scala.collection.immutable.Queue[Nothing] = Queue()

scala> res4.enqueue(1)
res5: scala.collection.immutable.Queue[Int] = Queue(1)

scala> res5.enqueue(2)
res6: scala.collection.immutable.Queue[Int] = Queue(1, 2)

scala> res6.dequeue
res7: (Int, scala.collection.immutable.Queue[Int]) = (1,Queue(2))

PriorityQueue

  • enqueueした順によらず、自動的に中身をソートして並べてくれるQueue(優先度つきキュー)
  • 可変のみ
  • 挿入削除時に並べ替えるコストが低く、先頭の値取得も高速に行うことができる
  • newBuilderメソッドの引数にOrdering型のオブジェクトを渡すことでソートする順番を変更できる
  • ソートにコストがかかり、先頭の値だけを取得すれば良いような処理を、非常に高速に処理することができるコレクション

タプル

  • 異なる型のオブジェクトを結合して、1単位として渡せるようにしたもの
scala> (1, "hello", Console)
res21: (Int, String, Console.type) = (1,hello,scala.Console$@2de22e7)

参考

Scalaスケーラブルプログラミング第3版 - インプレスブックス

N予備校 プログラミングコース

【React】親コンポーネント更新による子コンポーネントの更新処理を減らす

Reactでは親コンポーネントが更新されると、その子コンポーネント全てに更新処理が走るのですが、値に変更がない子コンポーネントは更新処理を行わないようにできないものかと調べたら、良さそうな方法が見つかったので備忘録として残します。

参考:

ReactでshouldComponentUpdateを使ったチューニングの効果と注意どころ - Qiita Reactの再レンダリングをなるべく減らす - Aqutras Members' Blog

Reactコンポーネントの更新処理をキャンセルするためには、shouldComponentUpdate内でfalseを返してあげれば良い。(デフォルトではtrue) なので、子コンポーネントpropsstateに変更がなければ、shouldComponentUpdate内でfalseを返す処理を実装すれば、データの更新がない子コンポーネントの更新を行わないようにできる。

propsstateが全て値型か、参照型を含むかで異なる実装を行う。

全て値型の場合

react-addons-pure-render-mixinを使うと簡単に実装できて良い。

import PureRenderMixin from 'react-addons-pure-render-mixin';

class HogeComponent extends React.Component {
  constructor(props) {
    super(props);
    this.shouldComponentUpdate = PureRenderMixin.shouldComponentUpdate.bind(this);
  }

  render() {
    return (
      ...
    )
  }
}

値型だけでなく参照型を含む場合

lodashの _.isEqual 関数を用いてオブジェクトを比較する。 もちろん値型も見てくれるので、react-addons-pure-render-mixinを使わずに、この方法に統一しても良さそう。

import isEqual from 'lodash/isEqual';

class HogeComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    const propsDiff = isEqual(nextProps, this.props);
    const stateDiff = isEqual(nextState, this.state);
    return !(propsDiff && stateDiff);
  }

  render() {
    return (
      ...
    )
  }
}

とりあえず今の自分の環境では↑の2通りの方法を使って実装しています。

GulpとBabelでES2015をES5に変換する環境をつくる

前回の記事でES2015の文法について覚えたので、今回はES2015をES5に変換するための環境をつくります。

参考:

Node.js

Node.jsとは

サーバーサイドのJavaScript、非同期の処理が得意。

インストール

方法は色々とありますが、今回はnodebrewというバージョン管理ツールを利用してインストールします。 ターミナルを開いて以下のコマンドを実行。 nodebrewのインストールが開始されます。

$ curl -L git.io/nodebrew | perl - setup
========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================

インストール後、環境設定ファイルにnodebrewのパスを通してねと言われるので、.bash_profileをviエディタで開き、コードを追記します。

$ vi ~/.bash_profile
export PATH=$HOME/.nodebrew/current/bin:$PATH

sourceコマンドで.bash_profileをリロードします。

$ source ~/.bash_profile

nodebrewがインストールされているか確認。

$ nodebrew help
nodebrew 0.9.5

Usage:
    nodebrew help                         Show this message
...

nodebrewをインストールし終わったら、Node.jsの使用するバージョンを指定します。今回は安定版を指定しました。 -vコマンドでバージョンを確認してNode.jsのインストール完了です。

$ nodebrew use stable
use v5.10.1
$ node -v
v5.10.1

npmをインストール

Node.jsで書かれたツールを管理するnpmというパッケージ管理ツールをインストールします。Rubyでいうとgemのようなやつです。 プロジェクト用のディレクトリを作成し、ターミナルでその中へ移動します。今回はpractice/es2015devというディレクトリを作成しました。

$ cd practice/es2015dev

移動したら以下のコマンドでpackage.jsonを作成します。 対話形式で任意の項目を入力します。今回は全てEnterキーを押しました。

$ npm init

...
name: (es2015dev) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/XXXX/practice/es2015dev/package.json:

{
  "name": "es2015dev",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this ok? (yes) yes

入力を終えると、ディレクトリ内にpackage.jsonが作成され、中には上の入力内容が記載されています。

Gulp

Gulpとは

様々な作業を自動化できる「タスクランナー」と呼ばれるツールです。Web制作においては、css,js,画像などの圧縮、Sassのコンパイル、ベンダープレフィックスの付与、LiveReloadなどを全て高速に行ってくれます。

インストール

以下のコマンドでGulpをインストールします。-gはグローバル領域にインストールするコマンドです。

$ npm install gulp -g

--save-devコマンドでローカルにもインストールします。

$ npm install gulp --save-dev

するとpackage.jsonにGulpが記載されます。

  "devDependencies": {
    "gulp": "^3.9.1"
  }

Gulpがグローバルとローカル両方に入っているか確認します。

$ gulp -v
[22:01:40] CLI version 3.9.1
[22:01:40] Local version 3.9.1

Babelをインストール

ES2016をES5記法に変換するBabelをローカルにインストールします。

$ npm install babel babel-preset-es2015 gulp-babel --save-dev

.babelrcファイルを作成し、利用するpresetsを記述します。

{
  "presets": ["es2015"]
}

Gulpの設定ファイルをpackage.jsonと同じディレクトリに作成します。 Gulpはバージョン3.9.0からBabelを標準サポートするようになりました。設定ファイルの名前をgulp.jsではなくgulp.babel.jsにすることで、ES2015で設定ファイルを記述できるようになります。

'use strict';

const gulp = require('gulp');
const babel = require('gulp-babel');

// タスクの登録
gulp.task('js', () =>
  gulp.src('src/js/**/*.js') // 読み込むファイル
    .pipe(babel()) // babelを実行
    .pipe(gulp.dest('dist/js')) // 出力先
);

src/jsディレクトリを作成し、その中にES2015で記述したjsファイルを作成します。

const obj = {
  add: (a,b) => a + b
};

準備ができたので、gulpを実行します。

$ gulp js
[22:18:57] Requiring external module babel-register
[22:18:57] Using gulpfile ~/practice/es2015dev/gulpfile.babel.js
[22:18:57] Starting 'js'...
[22:18:57] Finished 'js' after 107 ms

/dist/jsディレクトリにES5変換後のjsファイルが生成されました。

"use strict";

var obj = {
  add: function add(a, b) {
    return a + b;
  }
};