vue3で実装するFlashMessage(テストもあるよ)(typescriptだよ)
動機
vue3を利用してライブラリ単体でも実装しやすくて、いろんな機能を使える練習問題にちょうどよさそうな題材ということでFlashMessage(ToastMessageとも言われているアレ)を思いつきました。
同様の実装は軽くググっただけでもライブラリとして溢れています。自前で実装するよりそちらを利用することも実業務では多いかもしれません。
しかしある程度切り出しやすい粒度の機能であれば自前で実装した方が長く見てもコスト安の場合もあるのでこれを機に実装してみました。
作ったものと各種ライブラリバージョン
github: https://github.com/michihiko-karino/vue3-flash-message
vue: 3.2.37
vite: 3.1.0
vitest: 0.23.2
typescript: 4.6.4
実装ポイント解説
作りましただけでは技術記事としてアレなので工夫点などを解説します。
基本的なことばかりですが誰かのためになれば幸いです。
teleport
を使おう!
teleport
はVue3から追加された機能で、Vue2ではPortalVueなどのプラグインで実現されていました。
teleport
はコンポーネントをHTML構造からテレポートさせる機能であり、モーダルなどがよく例にだされます。
FlashMessageもモーダルと同じように画面のFixedな位置に、色々なコンポーネントから呼び出される可能性のあるコンポーネントのため、同様にteleport
を使いましょう。
teleport
を使うとSpecで実際に表示されているかの検証がちょっとめんどくさくなってしまいます。
しかしこれには回避方法があります。テストの段落で説明します。
主なロジックはcomposablesにしてしまおう!
リアクティブ変数とそれを変化させるメソッドをVueコンポーネントから別のモジュールに切り出すことで、テストしやすさと型をシンプルにします。
FlashMessageという機能自体がシンプルなおかげでもありますが、リアクティブ変数が3つ、メソッドが2つだけで実装できてしまうのは嬉しいですね。
provide/inject
の活用と注意
FlashMessageを各コンポーネントに使ってもらうために、メッセージを表示するメソッドと消すメソッドを各コンポーネントから参照できるようにする必要があります。
そのためにprovide/inject
を使いました。
provide/inject
はコンポーネントの親子関係を部分的に無視し、親が提供したデータを子孫全体で利用できるようにする機能です。
簡易ストア実装や、アプリコンテキストなどで使われますが、今回のような閉じた機能を作る際にも使えるものだと思っています。
inject
するときの型引数を工夫しよう
inject
はそのinject
が何を返すかを指定できる型引数を渡せます。
FlashMessageを使う側のコンポーネントにはリアクティブ変数を参照されても困るので、メソッドだけが定義されたMutations
型を使い、
FlashMessageコンポーネント自体は、自分を表示するor消すメソッドに関心がないのでMessageState
型を使います。
こんな風にprovideされた値全てを取り出すのではなく必要なものだけにするといいでしょう。
provide
した値はprovide
したコンポーネント自身はinject
できない
注意: provide/inject
には辛いな〜と思う仕様があります。
provide/inject
のキーにSymbolが使えること、もしくはその値自身がある程度の 機能 をもつ場合、それらをまとめて別のモジュールとして定義したくなります。そしてprovide
するだけのメソッドを定義したくなります。
当然ですがprovide
するコンポーネント自体がその値を利用したい場合もあるでしょう。しかしながらモジュールの中でprovide
された場合コンポーネントは値への参照を失います。
この仕様のため私はprovide/inject
を利用する場合はapp level provide
を推奨します。これは文字通りcreateApp
の返却値でprovideする方法です。
今回の例でもapp level provide
を使っています。
テストを書く
teleport
のSpecにハマった
teleport
を使っている時wrapper.html()
では意味のある内容が表示されません。詳しくは↓のリンクを見てほしいです。
ですが内部のDOM的にはちゃんと描画されているのでdocument.〇〇
でHTMLを直接見てあげれば問題ありません。
composablesのテストは書きやすいし、モックしやすい
teleport
やprovide/inject
が登場するモジュールのSpecはセットアップが必要になります。
しかしuseMessage.ts
のようにVue実装でないモジュールに切り出してしまえばシンプルなSpecになりますし、セットアップも必要ありません。
網羅的なSpecはこちらで書き、コンポーネントのSpecでは代表的な例だけを検証すれば良さそうです。
またモジュールに切り出すことでモジュールモックもしやすくなります。
↑のようにモジュールごとモックすることで、実際にメッセージがHTML上で表示されているかを確認せず、メソッドが呼ばれたかを検証するだけで済むようになり、セットアップコードをへらすことができます。
最後に
CompositionAPIやVue3からの各種機能があるおかげでVue単体で実装できるものも増え、テストもしやすくなりました。
今回実装したFlashMessageは文字列を表示するだけの単純なものですが、ライブラリ標準機能を使いこなす、テストしやすいモジュールを考えることは普遍的な技術です。
定期的にこういうことをやっていこうと思いました。
以上
Discussion