ushumpei’s blog

生活で気になったことを随時調べて書いていきます。

React Navigationで画面遷移してみる

React Nativeで画面遷移したかったのでまとめました。

内容としてはcreate-react-native-appで作ったアプリで、2つのナビゲーター(StackNavigatorTabNavigator)を使ってみた勉強記事です。

注意としてはDrawerNavigatorは使わないことと、redux等との連携も書いてないことです。

記述方法等は公式ドキュメントをご確認ください。

概要

公式ドキュメント で紹介されている画面遷移用のライブラリはいくつかありますが、 クロスプラットフォームで動作するものと考えると選択肢に入るのは以下の二つかなと思いました。 二つとも綺麗なドキュメントが用意されています。

どちらを選んでもよかったのですが、 create-react-native-appで使ったアプリを書きたいと思ったので、 javascriptだけで書かれているReact Navigationで書くことにしました。

React Navigation

React NavigationはReact Nativeで画面遷移を実装するために、いくつかのナビゲーター(navigator)というコンポーネントを利用します。

ナビゲーターはルーティングしたり画面に引数をあげたりジェスチャーを制御したり、画面遷移に必要なものを提供してくれるものです(ざっくりです)。

標準で使えるナビゲーターとしては、StackNavigatorTabNavigatorDrawerNavigatorです。それぞれ以下のような特徴があります。

  • StackNavigator: 画面を積んでいくタイプのナビゲーター。Screen1 -> Screen2とかScreen1 -> Screen3とか画面を積んでツリーを作っていき、行きつ戻りつしていくという感じです。描画した画面内のコンポーネントを押したら別の画面に遷移させる、といったWebのリンクと同じようなケースで使う感じです。
  • TabNavigator: タブタイプのナビゲーター。[Screen1、Screen2、Screen3]のように並列に画面を依存させずに扱い、切り替えていく感じです。画面遷移はTabNavigator自身が責任を持つ感じです。
  • DrawerNavigator: ドロワータイプのナビゲーター。自分的にはタブが横なら、ドロワーは縦という程度の位置が違うくらいの認識です。ただ、タブに比べて登録できる画面数が多い(多くても比較的わかりにくくならない)ので、例えばユーザーによって画面数が変化するタイプのものであれば、これを使うのかなと思います。

通常はこれらをネストさせるなどしてアプリケーションの画面遷移を作っていきます。カスタムしたナビゲーターを作ることもできるようですが、まだいいかなと思います。(丸いナビゲーターとかARっぽくてカッコ良さそう)

初心者的にはネストさせ方がちょっと不明で困ります。よく見るアプリケーションでは、

  • TabNavigator(下タブ) or DrawerNavigatorをルートの親ナビゲーターとして選ぶ
  • それぞれの子にScreen(普通の画面)、StackNavigator、TabNavigator(上タブ)を任意に登録
  • そのそれぞれの子に画面を登録

のネストが2段くらいの構成になっていると思いますのでそれを真似します。

疑問: 認証、設定などの画面はどのように制御するのがいいのでしょうか?設定画面はモーダルを使って表示するのはよく見る気がします?認証画面もモーダルを使った表示を見ることがありますが、一瞬アプリが表示されてからシュルッと画面を乗っけるとかだと、裏側で画面が動かないように制御しなければいけない気がします。トップレベルのに認証用のナビゲーターをかませてネストを3段にするとか?

追記 2018/05/07: SwitchNavigator 使うといいかもしれないです

準備

ドキュメントを参考にインストールします。

$ create-react-native-app demo-application
$ cd demo-application
$ npm install --save react-navigation

StackNavigator

単純な使い方として、リストを表示して、押したらその詳細画面に遷移する例を書きます。

自動生成されるApp.jsに以下のコードを記述します。

gist.github.com


StackNavigator

という感じになりました。今回は単純な例なので遷移先のDetailは使い回しです。

TabNavigator

2つの画面を持つTabNavigatorを作成します。

  • App
    • MathematicsList: 先ほど作った数学リスト
    • AddMathItemScreen: 数学項目追加画面

やることは3つです、

  1. TabNavigatorのコンポーネントを作成
  2. 項目追加機能の実装
  3. 項目削除機能の実装

Step1 - TabNavigatorのコンポーネントを作成

App.jsを修正してTabNavigatorを描画するコンテナーコンポーネントを作成していきます。

...
import { TabNavigator } from 'react-navigation'; /* 追加 */

/*
 * TabNavigatorを作成
 * StackNavigatorと基本は同じ
 * 第二引数で画面下タブに表示されるアイコン色とラベル非表示を設定
 */
const Tab = TabNavigator({
  List: { screen: MathematicsList },
  AddItem: { screen: AddMathItemScreen },
}, {
  tabBarOptions: {
    activeTintColor: '#037aff',
    inactiveTintColor: '#737373',
    showLabel: false,
  },
});
/*
 * 二つのタブでデータを共有するためにAppコンポーネントをコンテナコンポーネント化
 * 今の所stateを持っている以外はTabNavigatorをラップしているだけ
 */
export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      mathematics,
    };
  }

  render() {
    /* screenPropsで各子供にmathematicsを渡している */
    return <Tab screenProps={{ mathematics: this.state.mathematics }} />;
  }
}

TabNavigatorStackNavigatorコンポーネントなのでjsx記法でrenderに書くことができます。screenPropsに値を入れると各タブでデータを受け取ることができるようです。ここでは各タブにmathematicsデータを渡すために、TabNavigatorの設定をして一旦Tabと名付けた後に、Appコンポーネントをコンテナ化し内部でscreenPropsを渡しました。

TabNavigatorで登録された画面にnavigationOptionsを設定していきます。AddMathItemScreenは以下のように作成しました。

...
import { Entypo } from '@expo/vector-icons';
...
/*
 * 項目追加画面を作成
 * 現在はタブアイコンの設定のみのモック
 */
const AddMathItemScreen = () => (
  <View style={styles.container}>
    <Text style={styles.paragraph}>This is AddMathItemScreen</Text>
  </View>
);
AddMathItemScreen.navigationOptions = {
  tabBarIcon: ({ tintColor }) => <Entypo size={24} name="add-to-list" color={tintColor} />,
};

tabBarIcontintColorTabNavigatorのオプションで設定していて、activeinactiveに応じて異なる値が送られてきます。Entypocreate-react-native-appで作成すると初めから使えるexpoライブラリに含まれているカスタムフォントのコンポーネントです。

AddMathItemScreenと同様にMathematicsListにもタブアイコンの設定を行います。

/*
 * StackNavigatorを変数に格納
 */
const Stack = StackNavigator({
  Detail: { screen: DetailScreen },
  List: { screen: ListScreen },
}, {
  initialRouteName: 'List',
});
/*
 * StackNavigatorをラップするコンポーネント
 * screenPropsにより親からもらったpropsを子にそのまま流している(手抜き)
 * mathematicsリストをAppコンポーネントに持たせているため、
 * ここで設定したものがListScreenに渡っていく。
 */
const MathematicsList = ({ screenProps }) => (
  <Stack screenProps={screenProps} />
);
/*
 * タブアイコンの設定
 */
MathematicsList.navigationOptions = {
  tabBarIcon: ({ tintColor }) => <Entypo size={24} name="list" color={tintColor} />,
};

また、親からscreenPropsが渡されてくるのでそれをListScreenに渡すためにここでも一旦StackNavigatorを変数に格納して、MathematicsList内で受け渡しを行います。(もっといい方法があると思うんですが。。。ちゃんとやるときは多分reduxとか使うのでこの辺りはなあなあにしておきます)

ちなみにListScreenは次のように値を受け取ります。

/* 引数screenPropsが親から渡ってくる */
const ListScreen = ({ navigation, screenProps }) => (
  <FlatList
    data={screenProps.mathematics} /* mathematicsを取り出す */
...


TabNavigator1

上記の変更をまとめたのが以下になります。

gist.github.com

2つのタブが画面下に表示され、片方の画面は先ほどのStackNavigatorがネストされている状態になっています。

追記: タブアイコンの設定箇所をTabNavigatorの定義箇所に変更することも可能なようです。StackNavigatorに関しても同様で、タイトルなど動的に変更する必要があるかどうかで分けるという感じです。

const Tab = TabNavigator({
  List: {
    screen: MathematicsList,
    navigationOptions: {
      tabBarIcon: ({ tintColor }) => <Entypo size={24} name="list" color={tintColor} />,
    },
  },
  AddItem: {
    screen: AddMathItemScreen,
    navigationOptions: {
      tabBarIcon: ({ tintColor }) => <Entypo size={24} name="add-to-list" color={tintColor} />,
    },
  },
}, {
  initialRouteName: 'List',
  tabBarOptions: {
    activeTintColor: '#037aff',
    inactiveTintColor: '#737373',
    showLabel: false,
  },
});

Step2 - 項目追加機能の実装

項目追加機能を実装するために、Appコンテナコンポーネントからstateをいじれる関数を、TabNavigator経由でAddMathItemScreenコンポーネントまで渡します。

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      mathematics,
    };
    this.addNewMathItem = this.addNewMathItem.bind(this);
  }

  /* 子供に渡す関数を作成 */
  addNewMathItem({ title, detail }) {
    this.setState({
      mathematics: [...this.state.mathematics, { title, detail }],
    });
  }

  render() {
    /* screenPropsで各子供にmathematics、addNewMathItemを渡す */
    return <Tab screenProps={{ mathematics: this.state.mathematics, addNewMathItem: this.addNewMathItem }} />;
  }
}

受け取ったAddMathItemScreen側でタイトル、詳細のテキスト入力、追加リクエスト処理を記述します。追加が完了したらListScreenに遷移するようにAlertOKボタンをトリガーに設定します。見栄えに関して多少のスタイルも追加しています。

class AddMathItemScreen extends React.Component {
  constructor(props) {
    super(props);
    this.state = { title: '', detail: '' };
    this.handleOnPress = this.handleOnPress.bind(this);
  }

  handleOnPress() {
    const { title, detail } = this.state;
    if (!title) return Alert.alert('Error', 'titleは必須です');

    this.props.screenProps.addNewMathItem({ title, detail });
    Alert.alert(
      'Notice',
      '項目を追加しました!',
      [{ text: 'OK', onPress: () => this.props.navigation.navigate('List') }],
    );
    this.setState({ title: '', detail: '' });
  }

  render() {
    return (
      <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
        <View style={styles.container}>
          <View style={styles.inputGroup}>
            <Text style={[styles.paragraph, styles.label]}>title</Text>
            <TextInput
              blurOnSubmit
              onChangeText={title => this.setState({ title })}
              style={[styles.textInput, styles.heading]}
              value={this.state.title}
            />
          </View>
          <View style={styles.inputGroup}>
            <Text style={[styles.paragraph, styles.label]}>detail</Text>
            <TextInput
              blurOnSubmit
              multiline
              onChangeText={detail => this.setState({ detail })}
              style={[styles.multiTextInput, styles.paragraph]}
              value={this.state.detail}
            />
          </View>
          <Button onPress={this.handleOnPress} title={'Add item to list'} />
        </View>
      </TouchableWithoutFeedback>
    );
  }
}


TabNavigator2

まとめると次のようになっています。

gist.github.com

Step3 - 項目削除機能の実装

項目追加機能同様、Appコンテナコンポーネントからstateをいじれる関数を渡します。削除の方法としては、配列のインデックスをキーに各詳細画面の削除ボタンから実行します(画面遷移で操作を限定させないと容易にバグりそうですね、理詰めでバグらせられそう)

削除メソッドを追加し、screenPropsで渡します。

export default class App extends React.Component {
...
  removeMathItem(index) {
    this.setState({
      mathematics: this.state.mathematics.filter((_, i) => i !== index),
    });
  }
...
  render() {
    /* screenPropsで各子供に渡している */
    return (
      <Tab
        screenProps={{
          mathematics: this.state.mathematics,
          addNewMathItem: this.addNewMathItem,
          removeMathItem: this.removeMathItem,
        }}
      />
    );
  }

次にDetailScreenのヘッダー右上に削除ボタンを設置する設定を書きます。indexListScreenからの遷移時にnavigation.state.params経由で渡すようにし、removeMathItemscreenProps経由で渡したのでそれらから取得できます。(ListScreenの変更は最後のまとまったもので確認できます)

DetailScreen.navigationOptions = ({ navigation, screenProps }) => ({
  headerRight: (
    <TouchableOpacity
      style={{ marginRight: 8 }}
      onPress={() => {
        Alert.alert(
          'Warning',
          '項目を削除しますか?',
          [
            {
              text: 'Delete',
              onPress: () => {
                screenProps.removeMathItem(navigation.state.params.index);
                navigation.goBack();
              },
            },
            { text: 'Cancel' },
          ],
        );
      }}
    >
      <Entypo size={24} name="trash" color={'red'} />
    </TouchableOpacity>
  ),
});

以上で削除機能が実装できました。コード汚いよ、コンポーネントに切り出せよ、と思いますが指針が決まらないのでとりあえず画面単位の分割に抑えています。

完成

完成しました。こんな感じで動いています。


TabNavigator3

gist.github.com

感想

引数の受け渡しはReduxとかでやりたいと思いました。なのでReduxない状態でのプロパティの渡し方は勉強不足です。すみません。

あと書き捨てのつもりだったのでgit管理していなくて、diffだけ見せるとかいう方法もあったかもという気がします。さらによくよく考えればgistの差分でもよかったです。記事が読みにくい感じですね。

React NativeのNavigatorがなくなった

ちょっと困った

SectionListが使いたいのでreact-nativeのバージョンをあげようと思ったら、 バージョン0.44にはNavigatorコンポーネントがないみたいであげられなくなっちゃいました。

なんか書いてある

Navigatorはお手軽だったので、結構使ってしまっていて他のライブラリで書き直すのはちょっと面倒です。

対策

応急処置としては指示通りにreact-native-deprecated-custom-componentsから使い、徐々に他のナビゲーションで書き換えていくつもりです。

感想

そういえば当時のドキュメントにNavigationExperimental的なコンポーネントが現れたりしてて、ナビゲーションに関して試行錯誤中な匂いはあった気がします。

これを機にナビゲーションを見直すのと、githubをwatchしていこうと思います。

追記

バージョンアップして試しにimport { Navigator } from 'react-native'してみたら、赤い画面で上の内容をディレクションしてくれますね。

動かなくなったりして迷うことはあまりないかもですね。

Reactコンポーネントをディレクトリにまとめる(ES6)

Reactのディレクトリ構成が全然わかりません。

Reactコンポーネントを分割する方法についてもメモです。ES6の文法の話だと思うのですが、正式なドキュメントが見つけられていないので間違いがあったら申し訳ないです。

書いたものをgithubに上げています。参考になれば幸いです。

webpackを使ってビルドを行なっており、設定ファイルは以下のようになっています;

  ...
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['es2015', 'stage-2', 'react']
        }
      }
    ]
  }

ディレクトリによる分割

例として以下のようにディレクトリを構成してみました。

src/
├── App
│   ├── App.js
│   ├── Bar.js
│   ├── Baz.js
│   ├── Foo.js
│   └── index.js
└── index.js

index.js内でimport App from './App'のようにモジュールとしてディレクトリを指定すると、ファイル./App/index.jsの内容がコンポーネントとして読み込まれます。./App/index.jsディレクトリ内のコンポーネントを組み立てておけば、呼び出し側はあたかも一つのコンポーネントのように扱うことができます。

ディレクトリでまとめることで、意味を保ったままコンポーネントの分割が行えそうです。「components」ディレクトリ以下にコンポーネントが大量に平置きされてしまうのを防げました。

感想

一方でコンポーネントの再利用性が下がる気もします。
一部のコンポーネントは他の箇所から再利用、他はディレクトリ内のコンポーネントを利用などしてしまうと、参照がわけわからなくなってしまいます。相変わらずディレクトリ構成は悩みどころです。
ちゃんと部品単位でのコンポーネント化を意識すると多少は回避できるかもしれません。

Railsからキーがキャメルケースのjsonを返す(jbuilder)

 Reactでアプリを書いていて思ったことのメモです。

 大概jsのオブジェクトのプロパティはキャメルケース(camel case)ですよね。
 Railsをサーバサイドにしたのですが、返却されるjsonがスネークケース(snake case)でした。

 クライアントサイドで書き換えるのも面倒だと思ったので、調べてみたところRails側で対応するのが楽なようです。

内容

 Rails(4から?)はjsonをリクエストした際に返却するオブジェクトの定義を、jbuilderというテンプレートエンジンで行うようです。

生成されている(例: _model.json.jbuilder)ファイルの先頭に

json.key_format! camelize: :lower
...

と記述するとキャメルケースに変更できました。

感想

 ちゃんとしたapiを作るならkey_formatも引数で指定できたほうがいいのだろうか?と思いましたが使う気がしないので何もしませんでした。
 クライアントからサーバへの送信の際にも書き換えが必要になると思いますが、どうしようかなーと考え中です。

 json.key_format!は単に文字列変換の関数を引数にとり、keyに適応する関数のようです。ここではcamelize: :lowerを渡していますが、他にも色々渡せますね。
 この関数はそんなに使い込む機会はないと思いますが、私は関数を引数に取る関数が結構好きです。だからどうしたという話ですが。

rubydoc: Jbuilder:key_format!

Promise + reduceで逐次処理

setIntervalでもいいですが、Promisereduceでもできるようです。

/* 逐次処理したいデータ */
const arr = [1,2,3,4,5];

arr.map( /* データの配列から`Promise`を返す関数の配列を作る */
  e => (
    () => new Promise(
      (res, _) => {
        setTimeout(
          () => {
            /* ここに処理を書く、ここではコンソールに出力 */
            console.log(e);
            res(e);
          },
          /* 処理の間隔 */
          1000
        );
      }
    )
  )
).reduce( /* reduceを使って、Promiseをthenでつなぐ */
  (p, c) => p.then(c),
  Promise.resolve() /* 初期値 */
)

一応のメリットは、処理を中断する際の記述がわかりやすくなることだと思います。

const arr = [1,2,3,4,5];

arr.map(
  e => (
    () => new Promise(
      (res, rej) => {
        setTimeout(
          () => {
            if(e == 4) return rej('4 is an unlucky number');
            console.log(e);
            res(e);
          },
          1000
        );
      }
    )
  )
).reduce(
  (p, c) => p.then(c),
  Promise.resolve()
).catch(
  e => console.error('Error!!!: ' + e)
)

ちゃんとする場合はsetTimeoutのコールバック関数内にtry...catchで記述するのがいいでしょう。

setTimeout(
  () => {
    try {
      if(e == 4) throw new Error('4 is an unlucky number');
      console.log(e);
      res(e);
    } catch(e) {
      rej(e);
    }
  },
  1000
);

感想

なんとなくメソッドチェーンにしたがる性分なのかもしれません。

React Native実機動作中のcommand+d

お疲れ様です。

とりあえず結論というか、言いたい事です。

React Nativeで作ったアプリを開発段階で実機にXcodeで転送した場合、
シミュレータでcommand+dしたときに出るメニューは、実機を振ると出すことができるようです。
(2017/02/22時点)

説明

最近、とりあえずiOSアプリのデモ作ってみないか?という縁がありReact Nativeを触ったりしています。

React Nativeはreact-native run-iosとか打てばシミュレータが立ち上がるのですが、カメラを使う機能などシミュレータでできないことは、Xcode経由で実機に転送する必要があります。

シミュレータではLive Reloadという、コードを修正するたびに再読込してくれる機能を有効にできるのですが、実機ではコードを修正するたびにビルド → 転送みたいな事をしないといけないと思っていました。

というのもLive Reloadを有効にするためのメニューは、シミュレータではcommand+dで出るのですが、実機ではショートカット打てないためです。

しかし実機に入れた場合でも、立ち上げ時にはローカル経由でjavascriptを読み込みに行っているようだし(完全にReact Native勉強不足です)。どうにかならないかなーと思い色々いじってみると、実機を振ったらメニューが出ました。その他やってみたこと

  • 音量ボタン同時押し
  • 二本指で画面下をスワイプ
  • 三本指で画面下をスワイプ
  • 二本指で画面をピンチ
  • 三本指で画面をピンチ

だからどうしたという感じですね。

感想

結局実機を振るとメニューが出たのですが、振る動作を使ったアプリを作りたい場合はどうするのでしょうか?

それはそうと毎回場当たり対応で知識が集積していかない感が集積しています。

ブラウザでテキスト読み上げるコンポーネント: react-voice-components

テキスト読み上げをしてくれるReactコンポーネントの存在を知りました。grvcoelho/react-voice-components

ブラウザで音声を使って何かしたいと思っていたので、試しに触ってみて、GitHub Pageにあげてみました。 以下ちょっと苦労したので、メモです。

Reactを動かせる環境の作成

半年前くらいに作ったコンパイルの環境がかなり古くなっていたので、facebookincubator/create-react-appで作り直しました。インストールに少々時間がかかりましたが、とても便利ですね。

npmで入れたreact-voice-componentsが動かない?

npmで入れたreact-voice-componentsだと、create-react-appで作った環境との相性なのか、コンパイルされずブラウザエラーが発生して動きませんでした。webpackの設定ファイルを上書けばいいようですが、とりあえず、git submoduleリポジトリコンパイル対象のディレクトリにクローンして使用することにしました。せっかくなので、本当に問題があるのなら、いずれ修正を送ってみようかと思います。

書いたコードです;

ushumpei/play-with-react-voice-components

GitHubページを公開する

GitHubページの公開方法は3つあるようで、mastergh-pagesブランチのindex.htmlからか、master/docsを使うか、だそうです。

今回はdocsを作成して公開しました。静的リソース(cssjs)の指定が絶対パスだったため、読み込まれなくて困りました。

感想

  • テキストから音声ファイルとして吐き出すことはできるか気になりました。
  • Reactで綺麗に書けるようになりたいです。
  • GitHubはとても太っ腹です。
  • リポジトリの作者がすでにデモページを作成してくださっているようです。。。

GitHubのIssuesを閉じる方法

GitHubのIssuesを閉じる方法、GitHubのHelpページでも紹介されていますが、commit messageに関して書かれているだけなので、PullRequestからでも閉じられるか確認しました。

確認内容

結論から言うと、PullRequestからでも閉じられます。

以下試したことです。これらは全て、マージするとIssue nを閉じてくれました。なお確認したのは、PullRequestの向き先がmasterの場合です。;

  • commit messageの1行目にClose #nが記述されたcommitを持つPullRequest
  • commit messageの3行目にClose #nが記述されたcommitを持つPullRequest
  • titleにClose #nが記述されたPullRequest
  • descriptionにClose #nが記述されたPullRequest

試してみたリポジトリ(特にどうでもいい内容です)。

余談

Closecloseでも問題なく、この単語以外には次の単語群が使用可能らしいです。

GitHubのHelpページ

Keywords for closing issues

The following keywords will close an issue via commit message:

close
closes
closed
fix
fixes
fixed
resolve
resolves
resolved

(転載日時: 2016/12/20)

Helpページでは、あるリポジトリから別のリポジトリのIssueを閉じる方法なども紹介されてます。

感想

PullRequestとIssueはID(#番号)の空間を共有していることに今更気がつきました。

repositoryのカタカナ表記をリポジトリとしましたが、レポジトリという発音をよく聞きます、どちらがいいのでしょう?

Yesodのquick startが重い

 題に掲げた問題を解決するための記事ではないです。HaskellのwebアプリケーションフレームワークYesodquickstartをDockerfileで行っただけの記事です。

Dockerfile

 Dockerfileを書きます。

FROM haskell:latest
MAINTAINER ushumpei
ARG project_name
RUN stack new $project_name yesod-sqlite \
  && cd $project_name \
  && stack build yesod-bin cabal-install --install-ghc \
  && stack build
CMD ["/bin/sh"]

 ビルドします。任意のプロジェクト名を引数に取れるようにARGで指定しています。引数を渡すには--build-argオプションをつけて、イメージ自体に名前をつけるために-tオプションをつけます。

docker build -t yesod_sample . --build-arg project_name=sample

 ビルドできたらコンテナを立ち上げます。めちゃくちゃ時間がかかります。。。

docker run -i -t --rm -p 3000:3000 yesod_sample /bin/bash

 立ち上がったらコンテナのシェルが起動します、/以下にARGで指定した名前のプロジェクトができているので移動し、起動コマンドを実行します。

cd /sample
stack exec -- yesod devel

 コンパイルマイグレーションが終わればlocalhost:3000で「Welcome To Yesod!」のページにアクセスできるようになります。

感想

 stack build yesod-bin cabal-install --install-ghcがめちゃくちゃ重くて面倒でした。DockerHubに重い部分がいい感じに済んでいるイメージとかありそうですね。dockerstackyesodとわからないことが二つ以上あるともうしっちゃかめっちゃかです。

Leap Motionを手に入れました

 Leap Motionを手に入れました。Leap Motionは手の動きをかなりの精度で取得できるセンサーです。たくさんの言語でプログラミング可能なAPIが提供されていますが、とりあえずJavaScriptAPIを使ってみます。また、各クラスの内容もざっくりと整理していこうと思います。

 ここでは、開発PCがmacなのでSDKV2 desktopを使います。Windowsの方は新しいSDKが使えるようなのでそちらを使ったほうがいいかもしれません。(VR関係に強化されているように見えます)

参考

とりあえず

 環境設定やインストールは省略します、公式サイトの指示通りにすれば問題ないはずです。

 仕組みを理解するために、とりあえずブラウザ上に手を表示させてみます。まずはセンサーの値を取れるか確認をするためのコードを記述します。

ブラウザのコンソールに吐き出すだけ

<html>
  <head>
    <!-- <script src="./leap-0.6.4.js"></script> -->
    <script src="https://js.leapmotion.com/leap-0.6.4.js"></script>
    <script>
      Leap.loop(frame => console.log(frame));
    </script>
  </head>
  <body>
  </body>
</html>

 LeapJSを外部リソースとして取得します。LeapはLeapJSがネームスペースとして確保しているオブジェクトのようです。

 値をとるにはLeap.loopにコールバック関数を渡します。この関数にセンサーが取得したframeオブジェクトが引数で渡されるので、それに対する処理を記述するのがアプリケーション作成の基本的な流れになると思います。

 frameオブジェクトの詳細はリファレンスに書いてあります。手、指、ツール(棒状のもの)などのデータをプロパティとして持っていてくれています。

プラグイン Rigged Handを使う

 LeapJSはプラグインという形で機能拡張できるようになっています。プラグインControllerクラスによって管理されます。各プラグインpluginメソッドにより登録しuseメソッドにより有効にしたり、useメソッドに直接関数を渡すことで、loop実行時にコールバックとして呼び出されるようになります。

 プラグインを使うための準備として、まずは先ほどの処理をLeap.loopを使わない記述方法に変えておきます。

<html>
  <head>
    <!-- <script src="./leap-0.6.4.js"></script> -->
    <script src="https://js.leapmotion.com/leap-0.6.4.js"></script>
    <script>
      const controller = new Leap.Controller();
      controller.use(
        () => ({
          frame: frame => console.log(frame),
        })
      );
      controller.connect();
    </script>
  </head>
  <body>
  </body>
</html>

 プラグインを追加する準備が整ったところで、リアルな手を表示することができるRigged Handというプラグインを追加してみます。

 プラグインを使用するにはleap.rigged-hand-0.1.7.jsの他に、いくつかのライブラリを読み込む必要があります。useメソッドでriggedHandを使用するようにします。

<html>
  <head>
    <!--
      以下から必要なライブラリを取得
      https://cdnjs.cloudflare.com/ajax/libs/three.js/r70/three.js
      https://js.leapmotion.com/leap-0.6.4.js
      https://js.leapmotion.com/leap-plugins-0.1.10.js
      https://github.com/leapmotion/leapjs-rigged-hand/blob/master/build/leap.rigged-hand-0.1.7.js
    -->
    <script type="text/javascript" src="three.js"></script>
    <script type="text/javascript" src="leap-0.6.4.js"></script>
    <script type="text/javascript" src="leap-plugins-0.1.10.js"></script>
    <script type="text/javascript" src="leap.rigged-hand-0.1.7.js"></script>

    <script>
      const controller = new Leap.Controller();
      controller.use('riggedHand');
      controller.connect();
    </script>
  </head>
  <body>
  </body>
</html>

   動作はこんな感じになりました。実際の手も一緒に撮影すればよかったです。。。


Leap Motion - Rigged Hand Demo

LeapJSのクラスに関する考察

 API Referenceによると、LeapJSには次のクラスが存在します。

制御

クラス名 概要
Controller Leap MotionAPIに接続するためのインターフェース。オプションや、フレーム更新時のコールバックの設定など、最もさわりそうなオブジェクト
InteractionBox Leap Motion Controllerに紐づいた表示領域を扱うオブジェクト?領域に応じたベクトルの正規化方法などを提供してくれるみたいです
Frame センサーがフレーム更新ごとに取得した手、指に関するデータを含むオブジェクト

パーツ

クラス名 概要
Pointable 指(Finger)やツールなどの抽象クラスで位置や方向、速度などを保持しているオブジェクトです
Hand 手のオブジェクトで、5本の指の配列や腕などプロパティに持っています。かなり豊富な情報を持っているようです
Finger 指のオブジェクトで、生えている位置や、手の骨の本数文の骨オブジェクトに関する情報を持っています。何指か、などの識別子も持っています
Bone 骨のオブジェクトで、指の骨を表しています。前後の関節を持っています

ジェスチャー

 ジェスチャーは多いので個々の概要は省略します。認識してくれるハンドサインという認識です。独自に追加できるかはちょっとわからないです。

  • Gesture
    • CircleGesture
    • ScreenTapGesture
    • KeyTapGesture
    • SwipeGesture

計算要素

クラス名 概要
Matrix math 行列のオブジェクト。WebGL用外部ライブラリを使っているようです
Vector math ベクトルのオブジェクト。WebGL用外部ライブラリに入っているvec3を使っているようです

感想

 API Referenceを読んで、勝手に各クラスの概要などを書きましたが、このドキュメントを和訳した方が随分人のためになるんだろうな、と思いました。(もしかしたら和訳されている?)

 まだまだわかっていないことが多いので、ご指摘いただければ幸いです。

screenコマンドの小さな話

 ターミナル操作をいい感じにできる仮想端末マネージャコマンドscreenを最近知りました。メモしておきます。

 私の理解では、ターミナルをラップして、通常のシェル操作に加え、セッション、ウィンドウ、領域という概念を取り込み、それらを操作できるようにするものです。

参考: Screen User's Manual

 screenを実行すると仮想端末のセッション(session)とそれに紐づく一つのウィンドウ(window)が起動します。ウィンドウ内ではいつも通りのシェルの操作に加え、セッションに紐づくウィンドウを複数起動できるようになるので、ssh等でサーバに接続した後で起動すると便利です。今まで複数ログインしていた場合、一度ログインしてウィンドウを複数起動することで済ますことができます。(TeraTermのセッションの複製に似た感じです。詳しく見ていないですが内部的に使っているのでしょうか?)

 使うことのメリットとして、

  • 複数ウィンドウを起動できるので、面倒な踏み台経由のssh接続時に結構楽できます。
  • 接続が切れてもセッションが残っていてくれるので、再度つなぎなおした時に作業をすぐに再開できます。一つのウィンドウで長い処理を走らせて寝かせておくこともできます。

 他にも、コマンドによるコピー&ペーストの操作をキーボードのみでできるようになることも、なかなか嬉しいことです。

基本的な使い方

 触っていてよく使っているコマンドを羅列します。

セッション管理系

コマンド 内容
screen 新しいsessionとそれに紐づいた一つのwindowを起動します。
screen -ls 現在起動しているsessionの一覧を表示します
screen -r 最後に繋いでいたsessionを再開します。sessionのidを渡せば指定したセッションにつなぎ直すことができます
Ctrl+a d session起動中に現在のsessionから抜ける(detach)ことができます。detach後もsessionは起動しているので、一覧で確認できます
Ctrl+a \ session起動中、session自体を削除します。一覧からも消えます。起動中の紐づいた全てのwindowも削除されます。私の環境だとCtrl+a Ctrl+¥になっていました

チートシート

 セッション起動後は、Ctrl+a ?でコマンドのチートシートが表示されます。私のメモの存在意義を奪いかねないですが、ただ表示されるコマンドがアルファベット順なのでちょっと見にくいです。そこに漬け込みます。

 以下主にセッション起動後のコマンドになります。

ウィンドウ管理系

コマンド 内容
Ctrl+a c 新しいwindowを開きます
Ctrl+a k windowを削除します、正確には削除しますか?と(y/n)で聞かれます
Ctrl+a " windowの一覧を表示します。j,kでwindowを切り替えることができます
Ctrl+a N 現在開いているwindowの名前(Name?)を表示します
Ctrl+a n, Ctrl+a p 別のwindowに切り替えます。多分切り替え先はprevious,nextに対応していると思います
Ctrl+a Ctrl+a 切り替え前のwindowに切り替えます

領域管理系

コマンド 内容
Ctrl+a S windowを水平に分割します。windowが紐づかない新しい領域(region)を作成します
Ctrl+a | windowを垂直に分割します。windowが紐づかない新しいregionを作成します
Ctrl+a TAB 分割されたregion間を移動します
Ctrl+a X 分割時、現在有効になっているregionを削除します
Ctrl+a Q 分割時、現在有効になっていない他のregionを削除します

メタ操作系(未整理)

コマンド 内容
Ctrl+[ コピーモードに入ります。hjkl等のvimライクな入力で移動することができます。spaceを押すとコピー開始位置を指定、もう一度spaceを押すと選択範囲をコピーします
Ctrl+] バッファのコピーした内容をペーストします

使っていての感想

 色々あります。

  • Ctrl+aがシェルの行頭移動を奪ってしまいます。Ctrl+a aに行頭移動が振り分けられているので頭を切り替えて使っています。セッションの中にいる、と言うことを意識できるのでこれはこれでいいかもしれないです。キーバインドを変えるのは嫌なので私はこのままです。慣れました。
  • vimの画面分割と一緒に使うと結構混乱します。vimCtrl+w系の画面移動とごっちゃになったり、screenでのコピー範囲が行単位なので行番号入っちゃったりしました。これは対応方法がわからないですが、慣れました。
  • スクロールについて最初どうすればいいか不明でした。ログ出力等で流れてしまった時は、コピーモードに入って遡っています。別にコピーしたいわけではないのでちょっと気持ち悪いです。vimの気分で操作しています、less同様いまだに正しい移動操作がわかりません。
  • macにデフォルトで入っているscreenはヴァージョンが古いようで垂直分割ができないです(2016/12/14に確認)。homebrewで入れ直しました。あと、ssh接続時に便利とか書いていましたが意外にコマンド自体が入っていないことがあったりします、注意です。

 他にもウィンドウ操作を記録することができたりするのですが、使い道がわからず今のところ使っていないです。さらに他にも複数人で同じセッションを共有するマルチディスプレーモードという便利なものもあるそうですが、私はぼっちなので使っていません。

感想

 コピー&ペーストがキーボードから手を動かさずに使えるので、嬉しくてローカルでも使っています。早く使いこなせるようになるといいのですが。まだまだ不明点が多いです。

 上記内容に間違いがあれば申し訳ないですが、修正しますので是非教えていただきたいです。

dockerに関するメモ

golangを少しだけ試して見たくなったけれど、環境を作りたくなかったのでdockerを使って見ました。特に目新しい内容ではないですが、自分用にメモします。

構成というほどでもないですが、

  • コーディング: 自分のマシン
  • 実行環境: コンテナ

とするために、まずはディレクトリを作成し、そのディレクトリをコンテナと共有して起動します。

mkdir ~/my_golang_src
cd ~/my_golang_src
docker run -i -t --rm -v `pwd`:/my_golang_src golang /bin/bash

シェルが開くので、/my_golang_srcに移動してgo runなどでソースを実行できるようになります。

redisのレプリケーション

 redisレプリケーションがとても簡単だったのでメモです。勉強も兼ねてdockerを無駄に使っています。環境は以下です。

  • macOS Sierra
  • Docker for Mac Version 1.12.3-beta29.2

 とりあえずredisが入ったAlpine Linuxのイメージを作成します。my-alpineディレクトリを作りその中にDockerfileを以下の内容で作成します;

FROM alpine:latest
MAINTAINER ushumpei
RUN apk --no-cache add redis
CMD ["/bin/sh"]
$ docker build -t my-alpine .
$ docker run -it --rm  my-alpine

 redisがインストールされた状態でAlpine Linuxが起動しました。

単一マシン上で

 先ほどのmy-alpineを使って、同一ホストで複数のredisを起動し、master、slaveのレプリケーション構成を行ってみます;

/ # redis-server --port 3679 &
/ # redis-server --port 3680 --slaveof localhost 3679 &

 本当はちゃんと設定ファイルを書いたほうがいいんだと思います。レプリケーションを確認すると;

/ # redis-cli -p 3679 INFO
...
# Replication
role:master
connected_slaves:1
slave0:ip=::1,port=3680,state=online,offset=519,lag=1
...

/ # redis-cli -p 3680 INFO
...
# Replication
role:slave
master_host:localhost
master_port:3679
...

/ # redis-cli -p 3679 set hoge fuga
OK
/ # redis-cli -p 3680 get hoge
"fuga"

とちゃんとなっているようです。

docker

 docker-composeでmaster: 1台、slave: 2台の構成を作ってみようと思います。新たにディレクトリ(docker-redisにしました)を作成し、その中にdocker-compose.ymlを以下のように作成しました。

version: '2' 

services:
  master:
    image: redis:latest
    restart: always
    ports:
      - 3679:3679
  slave_1:
    image: redis:latest
    restart: always
    ports:
      - 3680:3679
    command: redis-server --slaveof master 6379
  slave_2:
    image: redis:latest
    restart: always
    ports:
      - 3681:3679
    command: redis-server --slaveof master 6379

 slave_1slave_2からはmasterhostmasterで参照できるようになっているようで、分かるまで悩みました(参考: Compose のネットワーク機能)。docker-compose upで起動します。

 動作確認を先ほどのmy-alpineから行います。--netオプションで先ほどのdocker-compose upで起動したコンテナたちのネットワークに参加します。こうすることで、masterなどのホストを参照することができるようになります。

(ネットワーク名はデフォルトではディレクトリ名_defaultになるらしく、docker network lsでも確認できます。ディレクトリのハイフンが無視されています、どういうルールでしょう?)

$ docker run -it --rm --net dockerredis_default my-alpine

/ # redis-cli -h master INFO
...
# Replication
role:master
connected_slaves:2
slave0:ip=172.19.0.4,port=6379,state=online,offset=2367,lag=1
slave1:ip=172.19.0.2,port=6379,state=online,offset=2367,lag=1
...

/ # redis-cli -h slave_1 INFO
...
# Replication
role:slave
master_host:master
master_port:6379
...

/ # redis-cli -h slave_2 INFO
# Replication
role:slave
master_host:master
master_port:6379
...

/ # redis-cli -h master set hoge fuga
OK
/ # redis-cli -h slave_1 get hoge
"fuga"
/ # redis-cli -h slave_2 get hoge
"fuga"

感想

 redisレプリケーションってすごく簡単にできるように思えました。pub/sub使って何か作ってみたいです。

¥eあるいはedit

 mysqlの対話環境でちょっと長い処理を実行したくなった時のメモです。

 mysqlコマンドを実行すると対話環境が起動します。ただ改行を含んだクエリを書こうとすると、どこか間違えた時に書き直すのが非常に面倒です。

 そういった時は¥eまたはeditコマンドを使うと便利です。コマンドを実行するとエディタが起動し直近に実行したクエリが表示されます。編集を行い保存して閉じるとコマンドを抜けて対話環境に戻ります。あとは;を入力してエンターキーを押せば内容が実行されます。毎回エンターキーを押すのも面倒な場合は、¥e;で実行します、この場合はエディタを閉じたら即時実行されます。

 開くエディタは選べるようです。私の環境ではデフォルトはviでした。これは環境変数EDITORで設定できます。(export EDITOR=vimとか設定しときました)

 調べたいクエリがあった時に、

  1. mysql> ¥e;実行
  2. vimが開くので挿入コマンド:a!でコピーしてきたクエリをペーストする
  3. 適宜修正して:wqvimを終了する
  4. 結果を確認し、必要なら1に戻る

 という感じで使っていました。

 ちなみに¥eを実行した時に編集しているファイルは/tmp以下に作成されていることが確認できました、しかしこれはエディタを閉じると削除されるようです。ちゃんと取っておきたいなら保存した方がいいと思います。(viなら:w ~/hogehogeなど)

感想

 rails consoleでもeditコマンドが使えるみたいですirbはダメか、コマンドが違うようです?)。他の対話環境ではどうなんでしょうか?

追記: 2017/01/13 - 今手元にあるrails 5で試してみると、editコマンドは使えないみたいです。動作が確認できていた環境はすでに手を離れてしまったので再確認できません。。。何かわかり次第追記します。

git logの折り返し

 gitのlogdiff表示時の、文字列の折り返しの切り替え方をメモします。

 自分の環境(mac 10.12terminalgit version 2.8.4 (Apple Git-73))ではデフォルトで折り返しがされていたのですが、以下の指定で折り返しが無効になりました。

折り返し無効

$ git config --global core.pager "less -S"

折り返し有効

$ git config --global core.pager "less -r"

 (デフォルトで有効だったので、折り返し有効のコマンドは未確認です。。。)

 git log --graph --decorateなどでログを見たい場合は、折り返さない方が見やすい気がします。

 ちなみにcore.pagergit loggit diffで表示するときの出力コマンドを指定しているそうで、lessSオプションをつけて折り返しの制御が行われているようです。catとかも指定できるようです。moreは私の環境では文字化けしました。