Svelte/SvelteKitのbind:filesに潜むバグたち
<input type="file">要素を手なずける
- Svelte
- SvelteKit
投稿日:
もくじ
はじめに
SvelteKitで<input type="file">
要素を扱おうとしたところ、複数のバグに遭遇した。
いずれもGitHubでissueが立っているが、発見するまでにかなり時間がかかったので記録に残しておく。
bind:files
やon:change
属性の位置によるバグ
<input>
要素の属性内でbind:files
を記述する位置によって、値が正常にバインドされなくなるバグがある。sveltejs/svelteリポジトリ上にある類似のissueは「解決済み」としてマークされているが、今もon:change
属性などと組み合わせた際に同様の症状が出ることがあるようだ。
以下に変更前後のコードを掲載する。
<!--変更前(正常に動作しない)-->
<input type="file" accept="image/png,image/jpeg,image/webp" bind:files={files} on:change={callBackFunction} />
<!--変更後-->
<input type="file" bind:files={files} on:change={callBackFunction} accept="image/png,image/jpeg,image/webp" />
ファイルのバインド先変数とDOM要素の不整合
(2023/11/21追記)続報がないかとGitHubを確認していたところ、この問題を解決するとされるPRがマージされていた。このバグが発生しているのであれば、Svelteを更新することで解決するかもしれない。
bind:files
でバインドした変数にnull
やundefined
を代入することで値をクリアしようとすると変数の状態とDOM上の状態に不整合が発生してしまう。これによってファイルAを入力→バインド先変数にnullを代入→再びファイルAを入力という操作を行った際にバインド先の変数がnullだったにも関わらず、on:change
属性のハンドラが呼ばれないという直感に反する挙動が起きることになる。
この不具合に言及しているissueに付いたコメントにもある通り、HTMLの仕様ではHTMLInputElement.files
に空値を代入する操作は無視される。そのため正常に値をクリアするには要素のfiles
プロパティとバインド先の変数の両方に(new DataTransfer()).files
を代入しなければならない。
バグの再現方法や解決法については当該issueの作成者によるREPLが詳しいが、一応サンプルコードを掲載しておく。
<script>
let inputElement
let files
// inputElement.filesは変更されないままfilesのみがnullとなり、不整合が発生する
function clearFilesInTheWrongWay() {
inputElements.files = null
files = null
}
// 正しく値をクリアできる
function clearFilesInTheRightWay() {
inputElements.files = (new DataTransfer()).files
files = (new DataTransfer()).files
}
</script>
<!--"bind:files"は"bind:files={files}"の省略形-->
<input type="file" bind:files bind:this={inputElement}>
おわりに
ただでさえユーザーの少ないSvelteのエッジケースを踏み抜いてしまい大変な目に遭ったが、大量の英語記事とissueを読み漁ることでなんとか解決できた。
本稿によって同じ現象に苦しむ人をひとりでも救えれば幸いである。