PhRUG(Philippine Ruby Users Group) September 2017 Meetupに行ってきた
最近フィリピンに滞在しています。2017/09/21にフィリピンのRubyユーザーグループのイベントが行われるということで、海外勉強会ってどんな感じだろう?日本と違いがあったりするのかな?とか思い参加してきました。たくさん写真撮ってくるの忘れました。
とりあえず結論、強く思ったのは、フィリピンでも日本でも、技術系の勉強会は大体同じ雰囲気、ということです。
概要
イベントはmeetupから発見しました。PhRUG September 2017 Meetup 会場のホストはShield Foundryというベンチャー企業で、HPも綺麗な感じです、ただHPを見ても何を作っているかはよくわかりませんでした。場所はShield Foundryのオフィス、マニラのBGC(Bonifacio Global City)にあります。BGCにはいったことなかったですが、未来っぽい町でした。外資系企業がたくさんあるみたいで、金持ちが多いみたいな話です。
余裕を持って会場へ向かったのですが、交通渋滞、ビルを間違える、などのアクシデントで、会場に着いたのは開始30分後くらいになってしまいました…
会場のShield FoundryはいかにもITベンチャーといった感じでした。広さは3~40畳くらいで、オフィススペースとキッチンスペースに分かれています。オフィススペースには長机に沢山の椅子(アーロンチェア?)、各椅子の前の机にiMacとモニターがずらりと並んでいます。それぞれ漫画とか本とかも乗っていました。その他、バランスボールあり、サッカーボールあり、あとサッカーのアレ(二人でバーを回してボールを蹴り合うゲーム)もありました。テック系は世界共通なんだな、と少し安心しました。
受付を済ませてプレゼンしているキッチンスペースに入ると、ビール手渡され、真ん中のダイニングテーブルにはピザが置いてあり、ご自由にどーぞといった感じでした。
イベント
最初の発表は、会場に到着したときは既に始まっていて、ちょっと詳細がわからなかったです…。発表者はエンジニアの一日、のようなスライドを表示していました。とりあえず自分はタガログ語が一切できないので、英語でプレゼンしていてホッとしました。ただ英語がそこまでできるわけでもないし、トピック不明だったのでなんとなく頷いたりして過ごしました。
2番目の発表はrails
のパフォーマンス改善やテストなどの発表でした。bootsnap
, PhantomJS
, rack-mini-profiler
, New Relic
, その他諸々、をそれぞれいい感じに使って、計測、改善したよ!という感じです。そうですよね。納得な感じです。
3番目の発表はjupyter/notebook
を使って見た系の発表。マークダウン + コードでドキュメントかけるやつです。これ自体は先輩に教えてもらっていて知っていたのですが、バグレポートとかに使って、楽に可視化していこうよ!といった、やっぱりそう考えるよね的な共感を持ちました。
4番目の発表は、開発Tipsの紹介でした。git
のprecommit-hook
でコミット前にrubocop
してます、とか。rails
のサーバープロセスの優先順位あげたらなおった、とか。やっぱりコミット前にちゃんとしておきたいと思うよね!という再度、強い共感を持ったりしました。
感想
会が終わった後にエンジニアの方にフィリピンのプログラミング言語事情について尋ねて見たところ、大企業とか、求人の多さで言えばjava
, php
。ベンチャーはruby
, python
といった感じです。scala
はとてもいい言語だと思うけど学習コストが高いのと、まだ事例をそこまで聞かない、とのことでした。日本では既に事例があるので、このあたりの情報共有とか何かできるかも(逆もあるだろうし。そういえばtwitterは?scalaの事例としてはどう捉えていたのだろう?国内事例がない、という感じなのだろうか?)。
フロントエンドのjavascriptフレームワークに関しては、angular2
, react
。javascriptフレームワークはリッチすぎて、本当に欲しいものがその中の1つだったりする、という日本でも割と聞く話を伺うことができました。ionic
でネイティブを、とかいう話題もちらほら聞くようです。
ここまで書いて、強いて挙げるならフィリピンと日本のIT界隈の相違点はなんなのだろうか?という方向に思考が向かいました。フィリピンの方が学歴に関して厳しいのかな?という予想を最近持っていますが、まだちゃんと議論したことがないので結論としては不十分と思います。さらに交流を重ねなければと思います。
参加する前はブログを書くつもりがなかったため、少々微妙なレポートになってしまいました。来月もグループがあるそうなので、もし行くとなればもっといい感じにレポートしたいと思います。meetup便利です。
iOS11アップデートについていくためのSwift入門(主観)
2017/09/20に、iOS11がリリースされました。今回のアップデートでは機械学習やSiriアプリ、ARなど様々な新機能が使えるようになりました。リンクを見ていて、「ちょっとSwift読めるようになっておかないと、おそらく半年くらいつまらなくなってしまう…上がってくるニュースを見ているだけになってしまう…」と思ったのがこの記事を書こうと思ったきっかけです。
リファレンスとしてはAppleのサイト、The Swift Programming Language (Swift 4): A Swift Tourを使います。学習の目的はSwiftのコード(主に今後発表されるであろう「iOS11で追加された〇〇を使って見た系の記事のコード」)を読めるようになることです。
Hello, world!
を目標、と思ったのですが、ベタ書きで終わってしまいました。
print("Hello, world!")
概要
リファレンスの内容は、Swiftを書き始めるのに十分な知識を得るためのツアーです。記事がSwiftのプロジェクトとしてまとめられていて、ダウンロードできるのでそれを使って学習していきます。
セクションは次のようになっています;
- Simple Values
- Control Flow
- Functions and Closures
- Objects and Classes
- Enumerations and Structures
- Protocols and Extensions
- Error Handling
- Generics
本記事ではリファレンス内に記載されている情報で、主に書き方に関することをメモしていきます。すでに序文の時点で以下の二点が記載されています;
- グローバルスコープの処理は勝手に実行されるので、エントリーポイントとしてのmain関数が必要ない
- 文末にセミコロンは必要ない
Simple Values
- 変数宣言について、定数は
let
、通常の変数はvar
で宣言する:var hoge = 1
とか。 - 明示的な型の宣言は
let piyo: Double = 70
(後置型宣言) \()
で文字列に変数を埋め込める。"template \()"
- 3ダブルクォート
"""
で複数行の文字列を囲むことができる - 配列リテラル
["hoge", "piyo",]
が使用可能 - [“hoge”: 1, “piyo”: 2]辞書も可能
- 空の辞書は
[:]
Control Flow
- if, switch, for-in, whileとかありますがこの辺は不思議なさそう
型?
でOptionalな変数を宣言できる(例var hoge: String? = "hoge"
)if let hoge = { ... }
でOptional変数から値を取り出すことができる、取り出すことができたらブロック内の処理を実行するという記法if var piyo = { ... }
ともかけるが、多分そんなに使わない- 取り出した変数のスコープはブロック内
- その他にOptional変数を扱う方法としては、
??
を使って、defaultValue ?? optionalValue
と言うように記述すると、値が取り出せる時のみ使用される switch
はcase
とdefault
で構成していきます。break
は不要、default
は必須です。0..<4
で、0,1,2,3の範囲のレンジを作成できる
Functions and Closures
- 関数定義は
func
、引数の定義はhoge: String
(後置型宣言)、アロー->
で戻り値の型を記述:func hoge(hoge: String) -> String { ... }
- 引数を名前付きで渡すことができる
hoge('fuga', piyo: 'giyo')
これは定義時にfunc hoge(_ hoge: String, piyo fuga: String)
と言うようにする。引数のラベル
と言うもので、_
はラベルなしを意味する。 - 高階関数も作れる:
(Int) -> Int
とかで型を宣言 - クロージャ使える:
{ (hoge: Int) -> Int in // クロージャの型と変数名 return hoge * 2 }
- 場合によっては型も省略できるらしいので、
{ hg in ... }
とかブラケットとin
が出てきたらクロージャとして読む。引数も$0, $1
と順番で記載されることがあるので、{ $0 < $1 }
とか書かれていても混乱しないように。
Objects and Classes
- クラスのインスタンス化は
ClassName()
- フィールドはベタ書きでいいが、外部からアクセスするには
getter
メソッドが必要 - イニシャライザー(コンストラクター)は
init
、クラス内のメソッドでインスタンス自身を参照するにはself
、デイニシャライザーはdeinit
- 継承は
class SubClass: SuperClass { ... }
setter
定義時のnewValue
パラメーターは予約語method?.hoge
でmethod
の戻り値があればhoge
を呼び出すと言うことができる
Enumerations and Structures
- enumはcaseで値を振っていく。ラベルから数値を取り出すのは
rawValue
、数値からラベルを作成するにはイニシャライザを使う(この時の値はOptional)。 - structで構造体を定義。だいたいの機能はクラスと同じだが、構造体は常に値渡しされる。
- class, struct, enumは似ている
Protocols and Extensions
protocol
でインタフェースのようなものが定義できる- メソッドに
mutating
宣言することで、インスタンスプロパティを変更するメソッドを定義できる(クラスの時は不要、struct,enumの時だけ) extension
で既存の型に機能拡張を行える、Protocolの機能を追加するのにも使える
Error Handling
do...catch
構文でかく、怪しいところにtry
をかくtry?
でOptional値を取り出す
Generics
- ジェネリクス
<Item>
で、Item
を変数のようにしようできる(数学でいう変数的な、x的な):var hoge = [Item]()
でItem
型の配列を宣言とか。 where
句もジェネリクス関連、親、子などの型の検証を記述できる
感想
以上、自分がSwift
を読む際に詰まりそうなところをまとめ終えました。逆に言えば詰まらなそうなところは、私の主観の範囲で無視しています。
Optional型の存在がちょっと気になりました。だいぶ面白そうです。またクラス、構造体、列挙型に関する扱いも違った見方を得られて興味深いです。自分としては列挙型はそこまで重要視していなかったのですが、Swift
をかくことでもっと本質をつかむことができるかもと、少し前のめりになりました。
iOS`界隈の盛り上がりに少しでもついていきたいと思った次第です。
文法の勉強のため簡単なObjective-CのコードをXcodeで実行してみた
前回、React Nativeのソースコードを読もうとして挫折したので、基礎文法を勉強するためにObjective-C
で何か書いて見ます。
その前にObjective-C
のwiki
を少し読んで感じをつかみます。とりあえず読むために必要そうなこと3つです。
書いて見る
環境: Xcode: Version 8.3.3 (8E3004b)
ではとりあえずhello world
します。別にアプリケーションが書きたいわけではないので、コマンドラインツールプロジェクトとして書きます(もっと言えばコマンドラインツールが書きたいわけではないですが、実行の構成方法が不明だったため一旦これでいかせていただきます)
まずXcode
を起動してFile > New > Project
を選択します。モーダルが表示されるので、macOS
タブのCommand Line Tool
を選択しNext
を押します。名前などは自由に設定します、ただし使用言語はObjective-C
にして、初期設定を終えます。
次にコードを書いていきます。すでにmain.m
(エントリーポイントとなるファイル)が生成済みだったので、そこに処理を記述していきます。クラスの定義の練習をするためにHello
クラスを作成しましたのでそれを呼び出します。(@
はコンパイラディレクティブの他にもリテラルを使用するときの接頭辞として使う、いやおそらくコンパイラディレクティブでリテラルを変換している?)
main.m
#import <Foundation/Foundation.h> #import "Hello.h" int main(int argc, const char* argv[]) { @autoreleasepool { Hello* object = [[Hello alloc] init]; NSString* message = @"Hello world!"; [object setMessage:message]; [object say]; NSLog(@"[object message]: %@", [object message]); } return 0; }
次にHello
クラスをNew > File
からmacOS
タブのCocoa Class
を選択します。こうするとHello.h
、Hello.m
ファイルが生成されます。内容を記述していきます。以下を定義しています、
- 挨拶文を保持する
message
変数 - 挨拶文を取得する
message
関数 - 挨拶文を格納する
setMessage
関数 - 挨拶文をコンソールに出力する
say
関数
です。
- Hello.h
#import <Foundation/Foundation.h> @interface Hello : NSObject { NSString* message; } -(NSString*) message; -(void) setMessage: (NSString*) s; -(void) say; @end
- Hello.m
#import "Hello.h" @implementation Hello -(NSString*) message { return message; } -(void) setMessage: (NSString*) s { message = s; } -(void) say { NSLog(@"say: %@", message); } @end
動かして見る
Xcode
のウィンドウ上部メニュー左辺りにある▶︎
(Runアイコン)を押してコードを実行します。
感想
コンパイラディレクティブをちゃんと覚えていけばある程度読めるようになると思いました。@autoreleasepool
はGCということでいいのかな?いやちゃんと覚えないといけないですね。とりあえず公式ドキュメントをこの辺から探して見ます。
書けるようになる必要があるか?という問いに関しては一瞬、「まあでもSwift
あるし、、、」とか思いましたが、まだまだ使われているため、やっておいて損はなさそうな気がします。
React Nativeの画像遅延読み込み(ライブラリのソースを読んで見る)
この記事を要約すると、「画像遅延読み込みの方法が知りたくて、ライブラリのソースコードを読んで、Objective-Cのコードにたどり着いて、次に進めなくなってしまい一旦諦めたけれど、これを糧にもっと勉強しようという気になった」という自己満話です。
概要
少し前までWebサービスのユーザーのインターネット回線速度をそこまで強く意識したことがありませんでした。
もちろんパフォーマンス計測はしていましたが、サーバー側の処理を改善することで対処していて、あくまで一般的なユーザーは回線がある程度の速度であることを仮定していましたし、回線が弱すぎるのはユーザー側の問題としている部分がありました。
この回線の強弱に関する考え方が私が感じた、モバイルアプリとWebの大きな違いでした。
モバイルでは回線が弱くなることが容易に起こりうるし、そのせいでアプリが操作不能になってしまうことは、ユーザーにとってストレスだと思います(操作不能にする方がいいケースもあると思います)。いや、といよりモバイルから閲覧するWebサービスとアプリとの違いでいうと、アプリだとオフライン状態でも使えるように作れる部分があるので、極力そうしていった方が親切だし、ユーザーも安心だよね、という話です。
多分モバイルアプリ開発者の方々からすると当然すぎることだと思うのですが、私がこのことに気がつくためには、いつまでたっても画像が読み込まれないまま動かない、という経験が必要でした。(頭が悪いです)
読んでみる
そんなこともあり遅延読み込みのライブライreact-native-image-progress
とかに触れたのですが、そもそもどうやって遅延読み込みを実現しているか、ソースを読んでみようというのが今回の内容です。
とりあえず一番はじめのリリース0.1.0
を読む;
- 基本的には
react-native
のImage
コンポーネント Image
の読み込み開始、進捗、終了イベントハンドラにコールバックを登録しているImage
のprogressイベントについて苦労したんだろうなと感じられる- 遅延読み込み方法は
Image
のコードを読まなければわからない
ということなのでreact-native
のImage
コンポーネントのソースを読む;
RCTImageView
を読むべきとわかる
ここからObjective-Cです、RCTImageView.m
を読む;
RCTDirectEventBlock
で詰まる…
すみません、詰まりました。
感想
知らないことがたくさん見つかりましたし、いかに色々なことを知らないままで使っていたかわかりました。予想として遅延読み込みに関してはjavascriptのfetch
メソッドとかで実装されてて、簡単に読めないかな、とか思って軽く記事を書き始めたのですが、普通にネイティブでした。
結局わからずじまいになってしまい、かなりダメダメなので、しばらくいろんなソースを読んでみることにすると思います。Objective-Cの基本的な構文も抑えて今回の詰まったところを解消したいです。あとReact Nativeの仕組みもちゃんと知りたくなりました。
余談ですがFacebookのライセンスの話、どう捉えるか考えなければ。。。
ハノイの塔
なんだか久しぶりにjavascriptでハノイの塔を解いて見ました。以下の関数は、number
枚のハノイの塔をtime
回操作した時の状態を計算するものです。特に目新しいものではないと思います。
const hanoiSnapshot = (number, time) => { // 円盤の移動が完了した以降の操作はエラーを表示(良いやり方かどうかは悩む) if (time > Math.pow(2, number) - 1) throw new Error(`${Math.pow(2, number) - 1}回目の操作時点ですでに塔は完成しています`); // 三本柱の配列データを定義 hanoi = [[], [], []]; // 1~number番目の円盤それぞれの場所を計算して柱に格納して行く for (let i = 1; i <= number; i++) { // 移動する方向 const direction = Math.pow(-1, number + i + 1); // 移動した回数 const moveCount = Math.floor((Math.pow(2, i - 1) + time) / Math.pow(2, i)); // 1番目の柱を位置0とした時の円盤の絶対位置 const absolutePosition = moveCount % 3; // 方向を加味して場所を0~2に正規化して決定 const position = (direction * absolutePosition < 0) ? (3 + direction * absolutePosition) % 3 : absolutePosition; // 柱に円盤を追加する hanoi[position].unshift(i); } return hanoi; }
感想
イメージとしては円盤は方向と速度を持っていて、3つの柱を移動しつつ、くるくる回って行る感じです。数学で三乗根の乗算を考えるときに書くような図をイメージするとわかりやすい気がします。
wgetでサイズ確認
ものすごく小さな話かつググったら速攻出ますが、自分にとっては切実な問題なのでメモします。
結論から言うと--spider
オプションです。
$ wget --spider ***URL.zip*** Spider mode enabled. Check if remote file exists. --2017-08-31 09:49:43-- ***URL.zip*** ... Length: 11121426 (11M) [application/zip] Remote file exists.
補足(curl)
たまにwget
入ってないよ、となる時があるのでcurl
の場合も追記します。普通にヘッダ取得しているだけです。
$ curl --head ***URL.zip*** HTTP/1.1 200 OK ... Content-Length: 11121426 ...
(単位byte
)
ちなみにファイルのダウンロード自体はcurl -O ***URL.zip***
なのですね。勉強不足です。
感想
一日の通信量上限のあるネット環境で生活しているため、迂闊に大きいファイルをダウンロードすると終わってしまう、と言う背景です。
URL
の拡張子がzip
な理由は特にないです。
辞書.appのデータ構造の補足
注意: 結論としては補足になっていません。。。RELAX NG
XMLスキーマをここで勉強した際のメモ書き程度になっています
先日書いた辞書.app
の記事ではあまり辞書を作ることに関して調べきれていなかったところがあるので追記します。先日の記事でも単語と意味を登録することはできましたが、MyDictionary.xml
の構造(辞書のデータ構造)について補足していきたいと思います。
MyDictionary.xml
の構造(辞書のデータ構造)
MyDictionary.xml
はRELAX NG
というxml schema言語で記述されています。xml
に明るくないためやや中途半端な説明になりますが、RELAX NG
は外部のデータセットを使用する前提で使われることが大半のようです(参考)
xml
なのでデータを定義するために、簡単な方法を提供してくれているそうです。例えば子要素はelement
、属性はattribute
などがあり、それらに対し1つ以上持つ<oneOrMore>
、つけてもつけなくても<optional>
、どちらか一方だけ<choice>
、などの制約をツリーとして書いていくことができます。私のイメージではタグを定義して作っていく感じです。
まず最初の方の行を見ていきます。
<?xml version="1.0" encoding="UTF-8"?> <d:dictionary xmlns="http://www.w3.org/1999/xhtml" xmlns:d="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> ... </d:dictionary>
ここでは、2つのネームスペース、xml
とhttp://www.apple.com/DTDs/DictionaryService-1.0.rng
のを宣言しています。またxmlns:d
の部分で、d:
というプレフィックスを宣言しているため、データセットを使用する際は語頭にd:
をつけて使用していくことになります。(ようやくd:entry
などd:
をつけて記述していた意味がわかりました)
続いてデータセットを見てどんな定義があるのかを見ていきます。内容は/Dictionary Development Kit/documents/DictionarySchema/modules/dict-struct.rng
に記載されております。(http://www.apple.com/DTDs/DictionaryService-1.0.rng
はアクセスしても404
でちょっと分かっていません。ネームスペースとしてだけ使っているのでしょうか?)
webで公開されているものもあるので、適宜そちらも参照ください。
まず初めに<grammer>
が全体を囲っていることから、Named patterns
と呼ばれるタグを分割して定義できるようにするパターンで記述していることがわかります(リンク先の セクション4. Named patterns)。
<grammer>
は、1つの<start>
と複数の<define>
を持ちます。<define name="hoge">
と定義したものは、<ref name="hoge">
により参照することができるため、定義を組み合わせて最終的な<start>
を組み立てていく形になります。
ここで定義されている主なものを見てみましょう;
dictionary
<define name="dictionary"> <element name="dictionary" ns="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <zeroOrMore> <ref name="style"/> </zeroOrMore> <ref name="dictionary.attlist"/> <oneOrMore> <ref name="entry"/> </oneOrMore> </element> </define>
dictionary
の定義は、
- 実態は
dictionary
タグ。アップルのネームスペースを属性として指定している。(ネームスペースの必要性の理解が曖昧です。名前が一般的なので、重複しないようにという慣習的なことでしょうか?それかプレフィックスを共通化するためとか関係している?)- タグは0個以上の
style
を使用できる - タグは1個以上の
entry
を使用できる - タグは
dictionary.attlist
属性リストを使用できるdictionary.attlist
はXTHMLのバージョンと国際化指定を属性として持っているようです
- タグは0個以上の
ref
では参照先がelement
なのかattribute
なのかわからないため、どう表現したら適切か悩みます(おそらく言語で表現しにくいからこそ、この記法が使われているのでしょうが)。
entry
<define name="entry"> <element name="entry" ns="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <ref name="entry.attlist"/> <zeroOrMore> <ref name="index"/> </zeroOrMore> <ref name="Flow.model"/> </element> </define>
entry
の定義は、
- 実態は
entry
タグ。- 0個以上の
index
が使用可 Flow.model
(なんだこれ。内部にhtml
書けることと関係している?)が使用可entry.attlist
が使用可entry.attlist
はid.attrib
属性が使用可能(<index id="hoge"
とか書ける)parental-control
属性が使用可能(任意)title
属性が使用可能(任意) <- (なぜなくてもいいのだろう?)
- 0個以上の
index
<define name="index"> <element name="index" ns="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <ref name="index.attlist"/> </element> </define> <define name="index.attlist"> <attribute name="value" ns="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <text/> </attribute> <optional> <attribute name="title" ns="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <text/> </attribute> </optional> <optional> <ref name="parental-control.attrib"/> </optional> <optional> <ref name="priority.attrib"/> </optional> <optional> <attribute name="anchor" ns="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <text/> </attribute> </optional> <optional> <attribute name="yomi" ns="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <text/> </attribute> </optional> </define>
index
の定義は
- 実態は
index
タグで、属性としてvalue
、オプションの属性としてtitle
、parental-control
、priority
、anchor
、yomi
などが使用可能
といった形になっています。
つまり?
つまり今のところ、そういうデータ構造になっているということがここを読むとわかるようになったというだけのことでした。。。
これらのデータ構造が辞書データとして取り込まれた時に実際どのように扱われるかをみていく必要があり、補足し切れていないです。
データ構造の読み方はわかった、ならばタグとしてどのような形をしているか、その子要素、属性はどのように扱われるか、それらのことを次にまとめていきたいと思います。
外国語学習のためにMacの辞書.appに追加する辞書を作成する
概要
Macでは設定すると三本指タップで単語の辞書検索が行えます、文章書いている時や読んでる時に作業を中断せずに意味を調べられるのですごく便利に使わせていただいています。
最近フィリピンで生活しているので、タガログ語を読み書きすることがたまにあります。わからない単語を調べるために辞書.app
に追加する辞書ファイル(.dictionary
)が欲しかったのですが、ネットを探しても見当たりませんでした。
html
形式の辞書を見つけたので、Appleの公式ガイドライン(Dictionary Services Programming Guide)から辞書ファイルを作成してみました。その時の手順をメモしておきます。
注意: 以下の手順を行うにはApple Developer Program
への登録が必要です
Dictionary Development Kitのダウンロード
ガイドラインの指示に従い、Downloads for Apple DevelopersからAuxiliary Tools for Xcode - February 2012
を検索してダウンロードします。
ダウンロードしたdmg
を開くと、Dictionary Development Kit
ディレクトリがあるので、/Developer/Extras/
以下にコピーします。その後、その中にあるproject_template
を自分用の辞書に書き換えていきます。(テンプレートなのでコピーとかして使っていきます)
テンプレートの中身はこんな感じです。
. ├── Makefile ├── MyDictionary.css ├── MyDictionary.xml ├── MyInfo.plist └── OtherResources ├── Images │ └── dictionary.png ├── MyDictionary.xsl └── MyDictionary_prefs.html 2 directories, 7 files
バリデーションツール: jing.jar
辞書ファイルはxml
で、RELAX NG
という記法で作成します。xml
フォーマットバリデーションのためjing
を、github
から落としてきてビルドしておきます。
$ git clone git@github.com:relaxng/jing-trang.git $ cd jing-trang $ ./ant
java
がいります。jdk
インストールしてJAVA_HOME
の設定もしておきましょう。
辞書の作成
MyInfo.plist
の編集
製作者などのメタ情報を記載していきます。特に配布の予定はないのでざっと変更したくらいです。各キーの説明はCreating Dictionaries > Table 2-1を参照ください。
一旦make
まだ辞書の内容はテンプレートのままですが、完成イメージを見たいのでとりあえずmake & make install
してみます。
$ make """/Developer/Extras/Dictionary Development Kit"/bin"/build_dict.sh" "My Tagalog English Dictionary" MyDictionary.xml MyDictionary.css MyInfo.plist - Building My Tagalog English Dictionary.dictionary. - Cleaning objects directory. - Preparing dictionary template. - Preprocessing dictionary sources. - Extracting index data. - Preparing dictionary bundle. - Adding body data. - Preparing index data. - Building key_text index. - Building reference index. - Fixing dictionary property. - Copying CSS. - Copying other resources. - Finished building ./objects/My Tagalog English Dictionary.dictionary. echo "Done." Done. $ make install echo "Installing into ~/Library/Dictionaries". Installing into ~/Library/Dictionaries. mkdir -p ~/Library/Dictionaries ditto --noextattr --norsrc ./objects/"My Tagalog English Dictionary".dictionary ~/Library/Dictionaries/"My Tagalog English Dictionary".dictionary touch ~/Library/Dictionaries echo "Done." Done. echo "To test the new dictionary, try Dictionary.app." To test the new dictionary, try Dictionary.app.
辞書.app
を開き、環境設定から自分の辞書を選べるようになっているので、探してチェックを入れます。これで三本指タップで自分の辞書が表示されるようになりました。
後は辞書の内容をちゃんと書いていくだけです。
MyDictionary.xml
の編集
今回は簡単な辞書を作成していきます。タガログ語から英語の翻訳はwebからスクレイピングしました。
単語の定義ファイルは以下のようになりました
<?xml version="1.0" encoding="UTF-8"?> <!-- This is a sample dictionary source file. It can be built using Dictionary Development Kit. --> <d:dictionary xmlns="http://www.w3.org/1999/xhtml" xmlns:d="http://www.apple.com/DTDs/DictionaryService-1.0.rng"> <d:entry id="dictionary_application" d:title="Dictionary application"> <d:index d:value="Dictionary application"/> <h1>Dictionary application </h1> <p> An application to look up dictionary on Mac OS X.<br/> </p> <span class="column"> The Dictionary application first appeared in Tiger. </span> <span class="picture"> It's application icon looks like below.<br/> <img src="Images/dictionary.png" alt="Dictionary.app Icon"/> </span> </d:entry> ... <!-- 単語ごとにd:entryタグを作っていきます --> <d:entry id="一意な値" d:title="単語名"> <d:index d:value="検索キー"/> <h1>Hoge</h1> <!-- 単語のの内容、htmlかけます --> </d:entry> ... </d:entry> </d:dictionary>
html
はMyDictionary.css
でマークアップ可能ですOtherResources
ディレクトリにアセット、例えばOtherResources/hoge/image1.png
を入れておけばhoge/image1.png
で参照できます
以上です。
感想
ものすごい必要最小限のものができました。 単語間のリンクとかうまくいっていない…今度調べないと…
何よりも辞書を作る作業がとても面倒でした。著作権フリーのものを使って、.dictionary
形式で配布したほうがいいかもしれません。というか絶対どこかに「タガログ -> 英語」とか「タガログ -> 日本語」あると思うのですが、なんか見つからないです…
あとhtml
から抜いてきたのも失敗でした、他の辞書フォーマットから変換する処理を書いた方が楽だったかと思います。
iTunes ConnectにアプリをアップロードしてTestFlightでテスト
概要
iTunes ConnectにアプリをあげてTest Flightする方法がわからなかったので、多分これでいけるんじゃないかという手順をまとめました。個人的にメモしておきます。間違い、訂正ありましたら教えていただきたいです。
前提としてApple Developer Program登録済みです
当記事で使用しているXcodeのバージョンは8.3.3です、7以下のバージョンでは「3. XcodeからArchiveをアップロード」で言及しているAutomatically manage sigining機能は使えないようです
手順
0. 既存アプリのリネーム(optional)
簡単のため既存のReact Nativeアプリを再利用します。他の環境の方は無視していただいて結構です。
React Nativeアプリのリネームは簡単です。react-native-rename
をインストールし、コマンドを発行します。
$ npm install -g react-native-rename $ cd path/to/project/root $ react-native-rename 新しいアプリの名前
1. Apple Developer Portalで新しいApp IDを作成
Developper Portalにログインし、Certificates, Identifiers & Profiles
画面のサイドメニューIdentifiers
> App IDs
をクリックします。画面右上の+
を押してApp ID作成に必要な情報を入力していきます。以下は各入力項目の説明です。
- App ID Description
- name: 整理しやすいような名前をつける
- App ID Suffix (以下どちらかを選択、今回はExplicit App ID)
- Explicit App ID > Bundle ID:
reverse-domain name style string
とか一意になるように名前をつける - Wildcard App ID > Bundle ID:
com.domainname.*
のように指定すると、App IDを複数の(iTunes Connectのレコードという意味での)アプリで使用できるらしいですが、今回は使用しません。
- Explicit App ID > Bundle ID:
その他は特に変更しませんでした、お好みで設定してください。
2. iTunes Connectで新規アプリレコードを作成
iTunes Connectにログインし、マイ App
画面の左上の+
を押して新規アプリに必要な情報を入力していきます。
今回は、
- プラットフォーム: iOS
- 名前: 新しいアプリの名前
- プライマリ言語: 日本語
- バンドルID: 先ほどApp IDを作成した時のバンドルIDを選択。
- セレクトボックスには
name - bundle_id
のように表示されます - 選択肢に表示されない場合はリロードして見てください
- セレクトボックスには
- SKU: バンドルIDと同じもの
を入力しました。
3. XcodeからArchiveをアップロード
3.1. アプリの情報の編集
新しいアプリの名前.xcodeproj
ファイルをxcodeで開いてGeneral
タブへ行き項目を編集します。(react-nativeであればプロジェクトルート以下のios/新しいアプリの名前.xcodeproj
にあります)
- Identity
- Bundle Identifier: iTunes Connectで新規アプリレコード作成の際に入力した値
- Version: 0.0.1
- Build: 0.0.1
- VersionとBuildは変えなくてもいいですがせっかくなので。
- Signing
- Automatically manage sigining: チェックを入れます、プロビジョニングファイルの作成などやってくれるのでおすすめです。
- Team: 自分のチームを選択してください
以上で設定終了です。
3.2. Archiveのアップロード
画面上部、Product
> Archive
を選択しアーカイブを実行します。しばらく時間がかかる処理です。アーカイブが完了すると、ウィンドウが立ち上がります。
注意: Archive
が選択できない場合があるので、こちらを参考にしてください。
アップロードしたいArchiveを選択し、ウィンドウ右のUpload to App Store...
を押すとアップロードの準備に入ります(先にValidate...
を押してからの方が堅実だと思います、今回はやりませんでしたが)。
チームを聞かれるので自分のものを選択すると、アップロード画面になります。内容が問題なければUpload
を押してください。これも、しばらく時間がかかる処理です。アップロードが完了しビルドが作成されるとiTunes Connectからメールが来るので、気楽に待ちましょう。
ただし、失敗することもあるので、20分くらい音沙汰がなければアップロード画面の結果にエラーがないか確認しましょう。(Validate...
してからのアップロードなら、多少は安心していいかもしれません)
注意: Upload
を押した後、処理の表示がAuthenticating with the iTunes store
で止まってしまうことがあるようです。全てに対応できる方法かはわかりませんが、この辺りが参考になりました
4. iTunes Connect でTestFlightのテストを開始
4.1. TestFlightの準備
再びiTunes Connectにログインし、マイ App
で先ほど作成したアプリを選択します。タブからTestFlightを選択すると、アップロードしたビルドが表示されているかと思います。
ビルドに「輸出コンプライアンスがありません」と警告が表示されています、この状態ではまだTestFlightでテストを開始することができません。ビルドの値を押すと、画面が切り替わり、コンプライアンス情報を提出
というボタンを押すとモーダルが表示されます。今回特に暗号化していないのでいいえ
を選択しました。モーダル右下の内部テストを開始
を押すと、テストの準備が整いました。
4.2. テストへの招待
サイドメニューのテスター & グループの小項目iTunes Connect ユーザを追加
か外部テスターを追加
を選択しテストへの招待を行います。今回はiTunes Connect ユーザを追加
から自分に対して招待を行いました。
5. 終わり
TestFlightからメールが届くので指示に従い、TestFlightアプリをインストールしている端末からテストが開始できるようになります!
感想
おそらくはこれでいいだろうというものをまとめてみました。自分の環境ではダメだったなど、ご指摘いただければ幸いです。
特に使いだしてから色々いじった気がするのでXcode周りの手順がやや不安です。
FlatListのデータ更新時に再描画されない
概要
ちょっと困ってしまって、検索が時間かかったので他の人の手助けになればと思いメモします。
問題
React NavigationとFlatListでリスト編集サンプルアプリを作成した時に、FlatList
のデータ更新時に再描画されない問題に遭遇しました。
- データを
FlatList
で表示する画面がある - 他の画面でデータを更新する
FlatList
の画面を開くとデータが描画されていない(更新したものだけでなく、元からあったものの描画されない)- ちょっとスクロールすると再描画される
なんだこれ?と悩んでしまいました。
解決
検索するとextraData
を設定しろとか色々見つかったのですが、このIssueから解決できました。
コメントで言及されているケースと同じ状態だったのでFlatList
にremoveClippedSubviews={false}
を追加してあげると再描画されるようになりました。
感想
removeClippedSubviews
は高速化のためのフラグだそうです。FlatList
がラップしているVirtualizedList
のドキュメントにこのプロパティに関する言及があります。またこのページに「バグを引き起こすかも」、という注意がされているのも確認できます。
上記の方法でもうまくいかないケースがあるそうなのでIssue
を読んでみると何か糸口が見つかるかもしれません。
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 );
感想
なんとなくメソッドチェーンにしたがる性分なのかもしれません。