はじめに
以下の投稿では入力ボックスだけを作成しました。
プログラマの性でしょうか、結局、メッセージの送信・表示もできるチャット画面全体を作成しちゃいました。
ここでは、実装にあたってポイントとなった箇所を記載していきます。
最終成果物
見た目はこんな感じです。右側に今回作成したチャット画面が表示されます。
適当にメッセージを入力して遊んでみてください。
尚、AIの応答メッセージ(dummy.json
)は ChatGPT に作成してもらいました。
実装時のポイント
カスタムストアを使って AI とのやり取りを隠蔽
カスタムストア を使うことで AI とのやり取りを store.js
ファイル内にまとめています。こうすることによって、コンポーネント側(~.svelteファイル)では AI とのやり取りの具体的な内容を意識することなく実装できました。
App.svelte
では store.js
から以下をインポートしています。
-
messages
ストア -
isInquiring
ストア -
inquiry
関数
<script>
import { createSession } from './store.js';
import ChatArea from './ChatArea.svelte';
const { messages, isInquiring, inquiry } = createSession();
const handleSubmitMessage = (text) => inquiry("あなた", text);
</script>
<ChatArea messages={$messages}
handleSubmitMessage={handleSubmitMessage}
isInquiring={$isInquiring} />
メッセージを送信するとどんなことが起きるかを簡単な図で表してみました。
実際には、<ChatArea>
全体が再描画されるわけではなく、更新が必要な箇所だけが再描画されます。
App.svelte
で実装するときに意識するのは、messages
と isInquiring
がストアであることと、実際の値を参照するために $
を付けること、ぐらいです。これだけでリアクティブな仕組みに対応できてしまいます。
こうなると、store.js
内で dummy.json
を使っているのか、もしくは何等かの Web API を使っているのかの違いは、コンポーネントの実装に一切影響を与えません。store.js
を修正するだけで本格的な(?)アプリケーションに移行できちゃいます。
tick を使って再描画後の値を確実に参照
ChatArea.svelte
では、メッセージが追加されると自動的に最下部までスクロールする仕様になっています。このスクロールをスムーズに見せるために tick を使っています。
tick は再描画が完了するのを待ってから処理を実行するための仕組みです。
説明するのがむずかしいので、tick を使う場合と使わない場合を動画で比較してみます。
tick を使う場合。
tick を使わない場合。
tick を使わない場合はスクロールが若干遅れていますね。tick を使うことでこの遅れを解消する効果があるわけです。
実装はこんな感じです。
...
$: {
// 自動的に最下部までスクロールさせる
messages;
!!mainElement && tick().then(() => mainElement.scrollTo(0, mainElement.scrollHeight));
}
...
tick()
は Promise
を返すので await
できるのですが、$:
ステートメント内だと await
できませんでした。仕方なく then()
で対応しました。
いけてない実装だと思うところ
MessageInput.svelte
では、text
変数が変更されたら resize()
を実行して自動的に高さを変えるようにしています。
こんな感じです。
...
$: {
text;
resize();
}
...
text;
の行は普通なら「何の意味もない行があるじゃん」って思いますよね...。
ただ、この行が無いと text
変数が変更されたことを検知できないので、自動的に高さを変えることができなくなってしまうのです。(前述の ChatArea.svelte
の自動スクロールの実装も同様に messages;
という行があります。)
以下の様に強引に if
文を設ける方法がありますが、これだと空文字になったときに resize()
が実行されないのです...。
...
$: {
if (text) { // 😥 text が空文字の場合は false 扱いなので resize() は実行されない
resize();
}
}
...
なんか、もやっとしますよね...
もっとうまい実装方法があったらぜひ教えてほしいです!
REPL の Tips
REPL では「JS output」というタブをクリックすれば、コンポーネントがコンパイルされた後の JavaScript を確認できます。
例えば以下のように、実際には $messages
という変数が定義されていて、component_subscribe
関数によって更新を検知したら $messages
に value
を再代入していることが分かります。
こういうデータを見せてくれるように設計されている点がこの REPL の良いところだと思います。
他にも「CSS output」で CSSを、「AST output」で AST(構文木)を確認できます。
まとめ
Svelte を使って ChatGPT 風なチャット画面を作成できました。
Svelte の カスタムストア を使うことでコンポーネントを修正することなく挙動を変えられるように実装できました。また、tick を使うことでより丁寧な UI/UX を実現できることがわかりました。
Svelte はフロントエンド開発にとって便利な機能がシンプルに実現されていると思います。Svelte の開発者の方々の設計力はすばらしいですね。