BsBsこうしょう

これは考えたことではなく思ったことです。

Ant DesignのUpload::customRequestに学ぶ、TypeScriptで型が分からないときのプログラミング

科学サイドのバックエンド、魔術サイドのフロントエンド。フロントエンド神社で祀るべし

動機

現在TypeScriptでプログラムを書いており、ファイルの入出力を伴う処理を実装している。具体的には、ユーザが選択したローカル環境にある画像を、ブラウザ上で映す仕組みを実装した。

サーバへアップロードしないため、バックエンドは必要なくフロントエンドだけで実装できる。MDNのリファレンスとQiita記事を参考に、以下のような処理を実装した。

qiita.com

Qiita記事と比べて、画像のアスペクト比を保つ処理などを入れていない(完成したら入れる予定。まずは動くことを大事に)。

ここで、プログラム全体にAnt DesignというUIを適用することにした。この箇所以外の移行は比較的スムーズに行えたのだが、ファイルの入出力だけはうまく対応するモジュールが見つからなかった。

近いのはUploadコンポーネントである。しかし、Uploadコンポーネントは自動でサーバにPOSTするお節介極まる機能がついているので、これを解除する必要があった。customRequestという機能を使えば解除できることが分かったが、これをどう書けばいいかが全く分からなかった。

解決法

1. 公式のドキュメントを読む

ant.design

Property Description Type
customRequest Override for the default xhr behavior allowing for additional customization and ability to implement your own XMLHttpRequest function

XMLHttpRequestを使って処理すれば良いことが分かるが、肝心の型がfunctionで不明。TypeScriptにおけるfunctionは何も言っていないに等しい。

2. ググる(英語)

ant design Upload customrequest typescript など。

stackoverflow.com

JavaScriptによるcustomRequestの実装が出てきた。しかし、JavaScriptなので型については依然として不明。

また、rc-uploadというコンポーネントをラッパーしているということも分かった。そこのcustomRequestの説明を見ると、こう書いてある。

name type description
customRequest function provide an override for the default xhr behavior for additional customization

ぐぬぬ

3. エディタの補完とライブラリのソースコードを見比べながら調べる

Ant DesignのUploadコンポーネントはここ。

github.com

また、rc-uploadのソースコードはここ。

github.com

コードの補完によると、customRequestの型は (options: RcCustomRequestOptions<any>) => void。Ant Designのソースコードから、引数の型であるRcCustomRequestOptionsが定義されている場所を探す。

ライブラリ独自の型なので、interface.tsxのようなファイルを探すと良い*1

UploadRequestOption に型が変わっている。これをGitHub内部でクリックして定義ジャンプを行うか、ページ内検索をかけると、以下のinterfaceがヒットした。

これはかなり使えそうだ。今回の興味はUploadされたファイルの中身にあるので、その中でもfileに注目する。fileの型はExclude<BeforeUploadFileType, File | boolean> | RcFileexport type BeforeUploadFileType = File | Blob | boolean | string; なので、目標であるFile型が取り除かれてしまい困ってしまった*2。しかし、File型を扱わずにUpload処理が行われているとは考えられないので、RcFileに注目する。

RcFileインターフェースの説明はすぐ下に書いてある。

RcFileインターフェースはFileクラスを継承している。したがってFileクラスのメソッド・インスタンス変数を活用できるため、最初のコードが活用できそうだ。めでたしめでたし。

4. 実装する

ここまで型を追いかけることができれば、2で出てきたStackOverflowのJavaScriptの実装も見通しが良くなる。それを参考に冒頭のプログラムを書き直したものが以下だ。

僕はTypeScriptに全然慣れてないので、良い書き方はできない。

ハマった点

  • onSuccessonProgressなど、null safeなプロパティ(?:で定義)されているものにアクセスするには、その手前で存在チェックを行う必要がある
    • 存在チェックはTypeScriptの拡張 assert で行うと良い。そのため、このブログのコードはBad実装である。*3
  • 型が複数考えられるインスタンスに対してある型に特有の処理を行うときには、手前でその型であるかをチェックする必要がある(instanceof を使う? typeofはガバすぎるので多分使っちゃダメ)

これで最初の処理をAnt Designに書き直すことができた。おしまい。

困っている点

しかしこうやって実装すると、なぜかAnt Designで使えていたおしゃれなアニメーション機能が使えなくなっていて困っている。実装を読む限りPOSTしていない以外に成功例との隔たりはないのだが……

POSTしていないからだろうか? ダミーのPOSTを用意して検証する必要がある。

アニメーション機能を追加する

最初アップロード完了通知が届く後に、アニメーションをするスレッドが終了して完了通知を受け付けるようになっているのが原因のようだった。setTimeoutを使って、onSuccessを送信するタイミングを遅らせることで解決した。

まとめ

  • TypeScriptで型が分からないときは、引用したライブラリ元の interface.tsx をたどっていけばよい
  • TypeScriptのNull存在判定は厳しい

*1:検索ではなぜか出てこない。

*2:File型(クラス)はJavaScriptのファイルストリームを扱うクラス

*3:assert は元々デフォルトで含まれている拡張だったが、そうでなくなったので npm install --save-dev assert でインストールする必要がある。