読者です 読者をやめる 読者になる 読者になる

ジンジャー研究室

長めのつぶやき。難しいことは書きません。

複雑な UI を持つ Web アプリの実装課題を洗い出す

Virtual DOM でとりあえずレンダリング周りの問題が解決していて、 Elm を使うと型まわりも解決するのだが、その先に課題が山積してきたので、いま考えていることをメモしておく。前提としては、一般的な GUI っぽいアプリをブラウザ上で動かすことを考えている。お役立ち情報ではないので悪しからず。これで 90 日以上ブログを更新していないとかいう広告も消えるはず。

ルーティング

GUI 延長のブラウザアプリという視点では、アドレスバーは付加機能に過ぎなくて、個人的には「そこは UI じゃないからいじらないで」と言いたいのだが、やっぱり「?q=hogehoge」とかを弄る人がいて面倒だなと思っている。このパラメータ部分は組み合わせ問題で「?a=foo&b=bar」というのがあった時にどうしても「aを指定したときはbは指定するな」等という暗黙の制約があり、その時に単に無視する戦略と URL を修正するのかという問題がある。

それから、 URL をモデルあるいは UI の状態と1対1に紐づけるような設計にしていると思わぬところでハマりまくる。シリアライズ時に、「?a=foo&b=bar」の「bar」の部分だけを更新したいが構わず全部更新してしまったりする。あるいは逆に全部を更新したい場合もある。例えば、何らかのキーをしていしてページを訪れた後に何かを検索したとして、検索は一時的なものだから最初のキーが保存されててほしい場合と、その検索が重要なので新しい URL になっててほしい場合と両方ある。アドレスバーを無視すると、「このページへのリンクをコピー」ボタンというのが良くある UI で、無難な選択肢だと思う。

あとは履歴に残すか残さないか問題。「ページが変わった」と認識されるタイミングで残すのが良いのだと思うが、個人的にはほとんど使わない。というのは、1ページ中に複雑な UI を持つアプリにフォーカスしているので、ページをまたぐなら普通にブラウザ機能で遷移すればいいんじゃないかと思っている。同じ理由で「/foo/bar」という普通の URL をクライアント側でルーティングする気があまりない。サーバーサイドを巻き込むのと、クライアントサイドで複数ページの状態を一括管理するのが面倒なので。

コピペなどの一般的な GUI 機能

コピペや Undo 機能などを持つアプリはブラウザと喧嘩する。テキストフィールドをアンドゥしたつもりが、別の個所も一緒に Undo されたりする。まだ試してないが、編集対象にフォーカスを持たせると干渉しないで済むと思う。

コピー状態は、アプリ内に保持せず何らかの方法でシリアライズしてクリップボードに貼るのが良いと思う。最初はページ内で完結するコピペでも、機能追加で Excel から貼りたいとか、逆に Excel に貼りたいというのが出てくるので、その時に2か所で状態を持っているとどっちか分からなくなり、やっかいなことになる。あとは、クリップボードからペーストする時にセキュリティの都合でただ Ctrl + V をハンドルするだけではダメなので、こちらもフォーカスを解決する必要がある。

Undo については、 Flux の延長みたいなシンプルな方法が高度なアプリでは全然通用しない。モデルを全保存する方法と、アクションを保存して差分適用する方法を両方試したが、どちらにせよ他に考えることがたくさんある。例えば、連続した同じ動作はまとめて戻るとか、ある時刻まで戻るとか。あとは、過去のデータにタイムスタンプを持っていると「戻した結果をセーブする」場合に非常に混乱したので、次回やるときは賢くやりたい。

通信の最適化

画面上でデータをあれこれ編集するようなアプリだと、編集状態をサーバ側に同期したり、他の人が編集した内容をリアルタイムに反映したりという要求が出てきて、通信が大量に発生するので最適化が必要になる。通信量的には、画面全体のデータではなく差分のみを送ると削減できる。通信頻度的には、いわゆるデバウンス(またはスロットル)と呼ばれる仕組みを使う。ただ、やっぱり複雑なケースになるとシンプルなデバウンスでは通用しなくなって、「複数のリクエストを上手くマージしてから送信する」ような仕組みが必要になり、非常に面倒だった。

リアルタイム同時編集については、今のところ楽観的ロックでやっていて、自分の見ているのがいつ時点のデータなのかというのをリクエストに含めて、その間に誰かが編集してたら「駄目だったよん」という風にしている。ダメだった場合にどうするのが適切なのか、悲観的ロックの方が良いのでは?については回答を持っていない。それから、他の人が編集した結果をどの頻度で画面に反映するかも悩みどころ。あまり頻繁でも煩そうだし、通信頻度も増える。

マウスイベント制御

画面上のオブジェクトをクリックしたりドラッグしたりという事を大量にやっていると、処理が干渉してくる。例えば、「クリックした場合は処理A、ドラッグした場合は処理B、ただしドラッグ終了時にはAの処理をしたくない」というのを、 onclick と onmousedown でやっていてぐちゃぐちゃになった。最終的に ClickEmulator のような実装を作る羽目になり、過去2回のイベントからクリックを判定するとか、過去4回のイベントからダブルクリックかどうかを判定するとかをやっていてすごく面倒だった。その辺の解決策が欲しい。

あとは、ブラウザのデフォルトの挙動なのか、なぜか背景にある画像をドラッグするような挙動になることがあり、再現性なく起こるので原因が分かっていない。preventDefault / stopPropagation の組み合わせと Virtual DOM の描画タイミング問題が複雑に絡まっていそうな予感がある。