👷

input[type="number"]をやめた話

2022/06/20に公開

ファンタラクティブのエンジニアの 太田 です。
数値入力コンポーネントを作成する際に type="number" を使用するのをやめたことについて書きます。

https://zenn.dev/052hide/articles/input-number-with-rhf-n-zod

input[type="number"]とは


input[type="number"] (PC)


input[type="number"] (スマホ)

数値を入力するためのinputで半角数値以外の入力を除外する

問題

validな数値しか受け付けない

数値以外の値が入力できないためTypeScriptでも扱いやすくよさそうに思えますが、使用するユーザーは戸惑うことがあるようです。
実際にあった問い合わせや、問い合わせから想像する状況は以下のようなものです。

  • 入力できない (全角入力してる)
  • 確定したら値が消えた (全角入力してる)
  • ペーストできない (全角でペーストしてる)
  • キーボードではなく、UI上の▲▼で入力しないといけないと思っている (大きい数字を入力できなくて諦める)

開発者にとっては当たり前のことでもユーザーにとっては当たり前ではないようです。
また日本ではIMEを意識する必要があるため厄介な問題だと思います。

対応方法

タイトルにあるとおり input[type="number"] を使うのをやめました。
ただし単純に代わりに input[type="text"] を使うだけでは不十分なため以下の検討を行いました。

案1

  • 値をstring型で扱い、親で数値として正しいか判定する

案2

  • number型のpropで受け取り、数値として正しい場合だけ親に伝え、不正な場合は前回入力した値に戻す。

案3

  • number型のpropで受け取る値とstring型のコンポーネント内で使用するローカル値に分ける
  • onChangeでローカル値を更新しつつ、数値として正しい場合は変更を親に伝える。不正な場合は不正な値として別propで親に伝える。

採用

今回は案3を採用しました。
実際にはpropsで分岐し、string型として値を返し親で判定を行えるような余地を残しています。
またサンプルのコードには入れていませんが、nullの判定やカンマの扱いなど制御しています。
案1のようにUI上は文字列として扱いsubmit時に数値として扱う方法は考え方としては好きですが、Formの数値フィールド毎に数値への変換処理を行う必要があり面倒なのでやめました。
案2は数字以外を入力した場合は入力が動いていないような挙動になり、今回の課題を完全に解消することはできないかもしれません。ただ処理はとてもシンプルです。

補足

ローカル値を持つことのメリット

フォーカスが当たっている場合は数字だけを表示し、フォーカスがはずれた場合にカンマ区切りや単位を付加したフォーマットに変換することが容易です。
これはローカル値だけに影響し、親へは影響しません。

ソフトウェアキーボード

input[type="text"] を使用するとスマホなどのソフトウェアキーボードが数値入力キーボードとして開いてくれなくなります。
inputmodeにnumericを指定して対応していますが、どうやらブラウザに依存するようです。 (IMEつらい)

あとがき

プライベートで制作したコンポーネントでは案2を採用しています。
こちらはVue2を使用しており、formに使用しているライブラリの都合上、バリデーションのトリガーに @input を使用しないといけなかったためです。
またさすがに数値入力でアルファベットや日本語を入力することは少ないだろうと判断し、全角数字の半角変換へ対応だけで問題ないと判断しました。

ファンタラクティブテックブログ

Discussion