React Navigationで画面遷移してみる
React Nativeで画面遷移したかったのでまとめました。
内容としてはcreate-react-native-app
で作ったアプリで、2つのナビゲーター(StackNavigator
、TabNavigator
)を使ってみた勉強記事です。
注意としてはDrawerNavigator
は使わないことと、redux
等との連携も書いてないことです。
記述方法等は公式ドキュメントをご確認ください。
概要
公式ドキュメント で紹介されている画面遷移用のライブラリはいくつかありますが、 クロスプラットフォームで動作するものと考えると選択肢に入るのは以下の二つかなと思いました。 二つとも綺麗なドキュメントが用意されています。
どちらを選んでもよかったのですが、
create-react-native-app
で使ったアプリを書きたいと思ったので、
javascript
だけで書かれているReact Navigationで書くことにしました。
React Navigation
React NavigationはReact Nativeで画面遷移を実装するために、いくつかのナビゲーター(navigator)というコンポーネントを利用します。
ナビゲーターはルーティングしたり画面に引数をあげたりジェスチャーを制御したり、画面遷移に必要なものを提供してくれるものです(ざっくりです)。
標準で使えるナビゲーターとしては、StackNavigator
、TabNavigator
、DrawerNavigator
です。それぞれ以下のような特徴があります。
- 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
に以下のコードを記述します。
という感じになりました。今回は単純な例なので遷移先のDetail
は使い回しです。
TabNavigator
2つの画面を持つTabNavigatorを作成します。
- App
- MathematicsList: 先ほど作った数学リスト
- AddMathItemScreen: 数学項目追加画面
やることは3つです、
- TabNavigatorのコンポーネントを作成
- 項目追加機能の実装
- 項目削除機能の実装
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 }} />; } }
TabNavigator
、StackNavigator
はコンポーネントなので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} />, };
tabBarIcon
のtintColor
はTabNavigator
のオプションで設定していて、active
、inactive
に応じて異なる値が送られてきます。Entypo
はcreate-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を取り出す */ ...
上記の変更をまとめたのが以下になります。
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
に遷移するようにAlert
のOK
ボタンをトリガーに設定します。見栄えに関して多少のスタイルも追加しています。
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> ); } }
まとめると次のようになっています。
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
のヘッダー右上に削除ボタンを設置する設定を書きます。index
はListScreen
からの遷移時にnavigation.state.params
経由で渡すようにし、removeMathItem
はscreenProps
経由で渡したのでそれらから取得できます。(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> ), });
以上で削除機能が実装できました。コード汚いよ、コンポーネントに切り出せよ、と思いますが指針が決まらないのでとりあえず画面単位の分割に抑えています。
完成
完成しました。こんな感じで動いています。
感想
引数の受け渡しは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
を渡していますが、他にも色々渡せますね。
この関数はそんなに使い込む機会はないと思いますが、私は関数を引数に取る関数が結構好きです。だからどうしたという話ですが。
Promise + reduceで逐次処理
setInterval
でもいいですが、Promise
とreduce
でもできるようです。
/* 逐次処理したいデータ */ 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つあるようで、master
、gh-pages
ブランチのindex.html
からか、master
の/docs
を使うか、だそうです。
今回はdocs
を作成して公開しました。静的リソース(css
、js
)の指定が絶対パスだったため、読み込まれなくて困りました。
感想
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
試してみたリポジトリ(特にどうでもいい内容です)。
余談
Close
はclose
でも問題なく、この単語以外には次の単語群が使用可能らしいです。
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アプリケーションフレームワークYesod
のquickstartを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に重い部分がいい感じに済んでいるイメージとかありそうですね。docker
とstack
とyesod
とわからないことが二つ以上あるともうしっちゃかめっちゃかです。
Leap Motionを手に入れました
Leap Motionを手に入れました。Leap Motionは手の動きをかなりの精度で取得できるセンサーです。たくさんの言語でプログラミング可能なAPIが提供されていますが、とりあえずJavaScriptのAPIを使ってみます。また、各クラスの内容もざっくりと整理していこうと思います。
ここでは、開発PCがmacなのでSDKはV2 desktopを使います。Windowsの方は新しいSDKが使えるようなのでそちらを使ったほうがいいかもしれません。(VR関係に強化されているように見えます)
参考
- wiki
- 公式
- JavaScript SDK Documentation — Leap Motion JavaScript SDK v2.3 documentation
- github.com/leapmotion/leapjs
- Javascript | Leap Motion Developers
Leap Motion 小型モーションコントローラー 3Dモーション キャプチャー システム [並行輸入品]
- 出版社/メーカー: Leap Motion
- メディア: Personal Computers
- この商品を含むブログを見る
とりあえず
環境設定やインストールは省略します、公式サイトの指示通りにすれば問題ないはずです。
仕組みを理解するために、とりあえずブラウザ上に手を表示させてみます。まずはセンサーの値を取れるか確認をするためのコードを記述します。
ブラウザのコンソールに吐き出すだけ
<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 MotionのAPIに接続するためのインターフェース。オプションや、フレーム更新時のコールバックの設定など、最もさわりそうなオブジェクト |
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
を実行すると仮想端末のセッション(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
の画面分割と一緒に使うと結構混乱します。vim
のCtrl+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
を無駄に使っています。環境は以下です。
とりあえず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_1
、slave_2
からはmaster
のhost
がmaster
で参照できるようになっているようで、分かるまで悩みました(参考: 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
とか設定しときました)
調べたいクエリがあった時に、
mysql> ¥e;
実行vim
が開くので挿入コマンド:a!
でコピーしてきたクエリをペーストする- 適宜修正して
:wq
でvim
を終了する - 結果を確認し、必要なら
1
に戻る
という感じで使っていました。
ちなみに¥e
を実行した時に編集しているファイルは/tmp
以下に作成されていることが確認できました、しかしこれはエディタを閉じると削除されるようです。ちゃんと取っておきたいなら保存した方がいいと思います。(vi
なら:w ~/hogehoge
など)
感想
(rails console
でもedit
コマンドが使えるみたいですirb
はダメか、コマンドが違うようです?)。他の対話環境ではどうなんでしょうか?
追記: 2017/01/13 - 今手元にあるrails 5
で試してみると、edit
コマンドは使えないみたいです。動作が確認できていた環境はすでに手を離れてしまったので再確認できません。。。何かわかり次第追記します。
git logの折り返し
gitのlog
やdiff
表示時の、文字列の折り返しの切り替え方をメモします。
自分の環境(mac 10.12
、terminal
、git 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.pager
はgit log
やgit diff
で表示するときの出力コマンドを指定しているそうで、less
にS
オプションをつけて折り返しの制御が行われているようです。cat
とかも指定できるようです。more
は私の環境では文字化けしました。