東京Node学園祭2日目に参加しました

東京Node学園祭

昨日に引き続き、今日もNode学園祭です。
今日はハンズオン中心のセッションでした。

Authentication Tutorial with Auth0

auth0.com

認証プラットフォームを提供するAuth0社のセッションで、実際にAuth0を触ったりReactを使ってAPIを作ってみたりしました。
このサービスのことは今まで知りませんでしたが、設定画面で登録するだけで、ソーシャル認証もメールのVerificationもすぐにできました。
基本的な機能は無料で使えるので、大規模サービスだけでなく、小さなサービスをすぐ作りたい場合にも役立ちそうです。

ハンズオンではReactのチュートリアルを使って自分のAPIを作ってもみたのですが、こちらはなぜかAPIの画面からログインができない状態で時間切れに。
とはいえAPIの仕組みを学ぶのは予想以上に楽しかったです。

github.com

LINE Bot Handson

Node.jsを使ってLINEのBotを作るハンズオンでした。
最近になってNode.js向けのSDKが公開されたおかげで、基礎編の部分(オウム返しBotの作成)はとても簡単に作ることができました。
講師の@n0bisuke氏が内容をQiitaにまとめてくださっているのですが、とてもわかりやすいのでおすすめです。

qiita.com

その後応用編として、様々な機能をもたせたBotの制作に挑戦。
参加された方は、単語をWikipediaで検索してくれるBot・すてきな画像を拾ってくれるBotなど、短時間でいろいろなBotを作っていました。

私はというと、ハンズオン終了後も数時間うなりながらBotと格闘。 最終的にこんなのができました。

f:id:Udomomo:20171126224619p:plain

「映画」という単語を入れてメッセージを送ると、Cinefilという映画サイトの最新記事をアイキャッチ画像とリンクつきで教えてくれます。 これについてはまたブログかQiitaで書くつもりです。とりあえず無事完成してよかった。

速く良く書けることを目指して

今日のハンズオンで驚いたのが、時間内にBotを完成させた人たちの速さ。
自分が作ったようなものを、だいたい30分ほどであっさり完成させていました。
確かに、大事なのは何をどうやって作るかを考えることであって、実際に手を動かすのはコードの文法さえわかっていればいくらでも速くなるはず。
文法で悩んでいるうちはまだまだだなと痛感しました。
まずは今日結局できなかったNodeSchoolのコンテンツに、近いうちに取り組みたいと思っています。

東京Node学園祭1日目に参加しました

東京Node学園祭2017

この冬から会社でフロントエンド担当になる予定なので、どんな空気感なのか知りたいと思って参加しました。
一応TypeScriptやReactを触ったことはあるものの、業務でやったわけではないうえしばらくブランクがあるので、周りとのレベルの差をひしひしと感じました。私は甘かったのだ...
自分がもっとNode.jsに詳しければきっと面白かっただろうな、と思う発表もいくつか。もっと精進します。

面白かった発表

もう資料が公開されてました。速いですね。

まず、QiitaのJorge Bucanan氏による「Make You a React」。
https://goo.gl/UKjXiC

JSXファイル作成からVirtual DOMのパースまでを自分で実装してみようという内容でした。
Virtual DOMって何なのかという理解があいまいなまま今までReactを使っていたので、とても学びになる発表でした。
サンプルコードもわかりやすかったです。学園祭が終わったら自分でも書いてみるつもりです。

そして、メルカリのきりん氏による「いまさら聞けないCORS対応」。

この辺りは前に業務で扱った経験があるので、当時の苦労を思い出してしみじみしました。
先輩から「公式ドキュメントを読め」と何度言われたことか...
CORSやPreflightの意味など、あいまいになりがちな知識がわかりやすくまとまっていました。あとでまた見返します。

明日は

NodeSchoolでJSの勘を取り戻したいです。書くの大事。

正規表現クロスワードが面白すぎるのでぜひ紹介したい

regexcrossword.com

名前のとおり、こういうやつ。

f:id:Udomomo:20171107214735p:plain

縦横に文字を入れていき、すべての行・列で正規表現が満たされるようにできれば正解。
正解した盤面を横に読んでいくと、文章になっている問題もある。
1マスだけの初級問題から、9✕9マスくらいの鬼問題までたくさん揃っており、やりごたえも抜群。

解き方としては、入る文字が限られている行・列から攻めていくのがよさそう。
例えば上の問題なら、一番左の列にはFIAのどれかしか入りえない。また、真ん中の行の最初の文字は必ずUOITのどれか になる。これらを合わせると、真ん中の行の左端のマスが決まる。
あとは正規表現の知識とあなたの粘り強さしだい。

正規表現って一見よく使いそうだけど、大半の用途はURLのバリデーションや簡単な分岐表現が多くて、いざがっつり必要になったときにけっこう焦ることが多い気がする。
こういうので遊んでおくと、後々になって大きなものが蓄積していくんだろうなあ。

ReactのSyntheticEventとは何なのか

Reactでイベントを作るとき、引数にeを渡す例がよくある。
例えば公式ドキュメントにもこんなコードが載ってる。

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

このeSyntheticEventを表すものらしいが、どんなものなのかいまいちつかめないのでいろいろ調べてみた。
特にこのページがわかりやすく、役に立った。

www.kirupa.com

通常のイベントとの違い

Reactを使わず、通常のJSでイベントを実装するときは、イベントの種類によって様々なインターフェイスがある。
例えばクリックイベントを作るならMouseEventを使い、キーボード入力をイベントリスナーとするならKeyBoardEventを使う。
イベントインターフェイスの種類によって、独自のプロパティが実装されており、ブラウザごとの対応状況も異なる。そのため、ネイティブのJSでイベントをクロスブラウザ対応させるのはなかなか大変らしい。

ReactのSynthetic Event

一方Reactの場合、全てのイベントをSyntheticEventとして扱う。
Synthetic Eventを使えば、各ブラウザのネイティブイベントを全てラップしており、何も意識しなくてもクロスブラウザ対応ができる。

そして冒頭に出てきたeは、イベントが発生した際にそれをSyntheticEventとしてイベントハンドラに渡す役割がある。
単なる引数にすぎないので名前は何でもいい。eventにしている人もいた。

SyntheticEventのプロパティ

SyntheticEventに用意されているプロパティは以下のような感じ。

boolean bubbles
boolean cancelable
DOMEventTarget currentTarget
boolean defaultPrevented
number eventPhase
boolean isTrusted
DOMEvent nativeEvent
void preventDefault()
boolean isDefaultPrevented()
void stopPropagation()
boolean isPropagationStopped()
DOMEventTarget target
number timeStamp
string type

どのネイティブイベントにもあてはまりそうな、一般性の高いものが選ばれている。

ネイティブイベント特有のプロパティを使うには

個々のネイティブイベント特有のプロパティを使いたい場合も問題ない。
SyntheticEventが、イベントの名前によって何のイベントをラップしているか識別し、それに合わせてプロパティを使えるようになる。

例えば、公式が作ったコードを少し修正して、以下のようなコードを書いてみた。
もともとのコードは、ボタンをクリックするとオン・オフが切り替わるというだけのものだったが、
今回の修正により、shiftキーを押しながらクリックしないと切り替わらないようにしてみた。

See the Pen Toggle - ShiftKey ver. by Udomomo (@Udomomo) on CodePen.

修正したのは以下の部分。
handleClick関数に引数eを渡し、e.shiftKeyで条件分岐させてみた。

  handleClick(e) {   // eを追加
    if (e.shiftKey) {  // e.shiftKeyによる条件分岐を追加
      this.setState(prevState => ({
        isToggleOn: !prevState.isToggleOn
      }));
    }
  }

なぜこれが動くかというと、render関数の中でonClickという名前でイベントを定義しているため。
これにより、SyntheticEventが「自分がラップしているネイティブイベントはonMouseEventだ」と認識し、ネイティブJSのMouseEventオブジェクトのプロパティであるShiftKeyを使えるようになったのだ。

イベントの名前とプロパティの対応関係は、公式ドキュメントに載っている。

SyntheticEventのPoolingについて

公式ドキュメントには、「The SyntheticEvent is pooled」とある。
これは、SyntheticEventオブジェクト自体が再利用されるということ。
SyntheticEventのコールバック関数の実行が終わると、プロパティの値が全てnullにリセットされる。

すなわち、あるSyntheticEventの中でプロパティに値を設定した後、それを非同期で呼び出す別の処理で使ったり、値を取っておいて後で別の関数の中で使おうとしても、nullしか返ってこない。
event.persist()を使えば、非同期処理でもプロパティの値を参照できるようになるらしいが、Reactのパフォーマンスが落ちてしまうようだ。

diffコマンドで全行違うと言われたので調べたら改行コードのせいだった

自分が作ったHTMLのコードが、非エンジニア向けに配られるExcel資料に載せられることになった。そのExcelファイルを見ると、一つのセルの中にコードがそのままコピペされて入っていた。
Excelファイルを作った人が「これでいいか確認して」と言ってきたので、そのコードをエディタにコピペして元ファイルとdiffコマンドで比べてみたら、なんと全行違うとのこと。
目で見るとどこも違っているように見えないけど...と思っていろいろ調べたら、違っているのは改行コードだとわかった。

改行コードの種類

改行コードには、CR (carriage return)とLF(line field)の2種類がある。タイプライター時代の名残りらしい。
それを引きずった結果、OSごとに異なる改行コードが使われるようになってしまった。

  • LF: Unix系、macOSなど -> \n
  • CR+LF: Windowsなど -> \r\n
  • CR: Classic Mac OS (バージョン9までのもの) -> \r

改行コードを確認する

実際に改行コードを確認してみる。

HTMLファイル

エディタを使えばなんとか確認できることが多いはず。
自分はSublime Text 3 を使っているが、改行コードを画面右下に表示するための設定方法があった。

beadored.com

Windowsと出たので、元のHTMLファイルはどうやらCR+LFらしい。確かに元ファイルは前のPC(Windows)で作った覚えがある。

Excelファイル

Excelにはcode()という関数がある。これは、使われている文字のASCIIコードを返してくれる。
これを使い、セル内改行に何が使われているのか確かめることができる。ASCIIコードと改行コードの対応は以下の通り。

  • 10: LF
  • 13: CR

さて、Excelのセル内改行は、WindowsだとLFMacだとCRらしい。Excel独自の仕様はやっかいだ。

www.dtp-transit.jp

最近PCをMacBookに変えたので、受け取ったExcelファイルでcode()関数を使うと、確かに13と出た。Excelファイルを作った人はWindowsを使っているので、元はLFだったのがCRに変換されたのだろうか。
どのみち、CR+LFではないらしい。

地味に怖い改行コード

今回のようなケースは、エディタなどを使って改行コードを全て置換すればなんとかなる。
ただ、怖いのは複数OSのユーザを対象としたシステムやソフトウェアを作る場合。プログラムの中で思わぬ変換や誤認識が起きて、気づきにくいバグの元になりうる。
以前「WindowsMacは全然違うよー」と冗談めかして言っていた先輩の顔が思い浮かんだ。苦労したんだろうなぁ。

Sublime Text3で書いたファイルをVagrant仮想環境に同期させる

うちのPython環境はVagrantで作ったゲストOSの中にある。
一方でエディタはSublime Text3を使っている。flake8で補完が効いてらくらく。
なので、ホストOSでSublime Text3を使って書いたファイルを、手間なくゲストOSにも同期させたいと思っていた。

Sublime Text3にはSFTPパッケージがあるので、最初はそれを使おうと思っていたが、それよりもVagrantで共有フォルダの設定をしてしまった方が楽だと気づいた。SFTPパッケージは$30かかるし。

rsyncを使う

ファイルの共有を楽にするためには、vagrantrsync機能を使うのがおすすめ。前提としてVagrantのバージョンは1.5以上が必要。
これを使うと、 vagrant rsyncコマンドを打つだけで同期ができる。
さらに、 vagrant rsync-autoとすれば、共有フォルダを監視してくれて、ファイル更新のたびに自動で同期される。

ちなみに、vagrant rsyncが有効なのは、ホストOS→ゲストOSという流れでファイル共有をするときのみ。
その逆をしたいときは、Vagrantの外部プラグインを使う必要がある。例えばvagrant-rsync-backなどがある。

共有フォルダの設定をする

Vagrantfileの中に設定を書き加えればよい。

www.vagrantup.com

このとき、rsyncを使うために、オプションとしてtype: "rsync"とする必要がある。 設定の例はこんな感じ。

Vagrant.configure("2") do |config|
  # other config here

config.vm.synced_folder "/Users/Udomomo/python_project", "/home/vagrant/python_project/",
   type: "rsync",
   owner: "vagrant",
   group: "vagrant"

end

【17/10/15 追記】 上記だけでは足りなかった。以下のようにしなければならない。

config.vm.synced_folder "/Users/Udomomo/python_project", "/home/vagrant/python_project/",
   type: "rsync",
   owner: "vagrant",
   group: "vagrant"
   rsync__exclude: [".git", ".gitignore", "tmp", "log", "cache"],
   rsync__chown: false

rsync__excludeは、ゲストOS上で同期の対象外とするファイルやディレクトリを指定する。.git.gitignorelogcacheなどを設定するとよい。
これが必要なのは、rsyncによる同期をするとホストOS上にないファイルは全て削除されてしまうから。ゲストOS側でgitレポジトリを作った後に同期をしたら、レポジトリが消えてしまうという痛い目に遭った。

また、rsync__chownは、rsyncによってゲストOS側のファイルのオーナーやグループ名などが変わらないようにするための設定。v1.6.3から追加されたとのこと。
【追記終】

後はVagrantを再起動させれば、設定が完了する。
vagrant rsync-autoとしておけば、ホストOSのUsers/Udomomo/python_project配下で更新されたファイルが、ゲストOSの/home/vagrant/python_project/配下に自動的に同期される。

ちなみにこの設定だが、複数個書くこともできる。新しいプロジェクトが始まるごとにVagrantfileに書き足し、不要なものを消せばよい。
共有フォルダを一つしか設定できないと、毎回そこに行ってファイルを取ってこないといけないので、複数個設定できるのは本当に助かる...。

TypeScriptの型推論について学んだ

型推論とは

最近TypeScriptを使って開発していたけれど、大きな勘違いをしていた。
強い型付けをする言語ということなので、変数を定義するときに毎回型を宣言しなければいけないと思っていたのだ。
例えばこんな感じ。

let x: number = 3;

人間から見れば、この number はノイズでしかない。3は数値だと、誰が見てもわかるはずだ。
入力が手間なうえ、可読性を下げてしまう。

この場合は以下のような記述で問題ない。

let x = 3;

TypeScriptは3が数値であることを知っており、xを数値型とみなしてくれる。これを型推論という。

「最も共通する型」による型推論

この機会にもう少し公式ドキュメントを読んで深掘ってみる。
例えば以下の場合は、Arrayであることは間違いないが、その要素の型は何と推論されるだろうか?

let x = [0, 1, null];

TypeScriptは、提供される候補の型から「最も共通する型」を選ぶ。これは、他の全ての候補と互換性がある、すなわち他の候補がその型からの派生した型であるという意味だ。
上の場合、Arrayの中にはnumberとnullの2つの型があるが、TypeScriptでは、nullは全ての型の部分型(subtype)とみなされる。
このため、上の変数xの型はnumber[]と推論される。

ちなみに、この方法で型が選べない場合は、推論の結果が空のオブジェクト{}になってしまうので、最初に示したように自分で型を書いてやる必要がある。

「文脈上の型」による型推論

さきほどの共通する型による型推論は、平たく言うと右辺をもとに左辺の型を判定するという流れだった。
しかし、その逆で左辺から右辺の型を判定することもある。

window.onmousedown = function(mouseEvent) {
    console.log(mouseEvent.button);  //<- Error
};

上の場合、mouseEventの型は定義されていないはずだが、なぜエラーが起きるのだろうか?
TypeScriptは以下のように推論するようだ。

window.onmousedown は、mouseEvent型の引数を取る関数であることを知っている
・このため、右辺の引数 mouseEvent は、 mouseEvent 型であると推論される
・しかし、 mouseEvent 型に button というプロパティはない。そのためエラーとなる
developer.mozilla.org

このとき、function(mouseEvent: any) {...} のように、引数mouseEventの型を明示していれば、推論が上書きされてエラーは生じない。

人間が快適にコードを書けるよう、影でいろいろな苦労があるんだなあとしみじみ実感した。