Theme
SD MILIEU

2022-5-22

[React] input[type=number]のvalueをnumberとして扱うかstringとして扱うか

概要

Reactでtype=numberである数値入力用inputを作る際に、valueをnumberとして扱うパターンとstringとして扱うパターンがあるが、どっちの方が良いかという話。

number版

type Props = {
  value: number
  onChange: (value: number) => Promise<void>
}
export const NumberInput = ({value, onChange}: Props) => {
  const _onChange: React.ChangeEventHandler<HTMLInputElement> = event => {
    onChange(event.target.valueAsNumber)
  }

  return (
    <input
      type="number"
      value={value}
      onChange={onChange}
    />
  )
)

string版

type Props = {
  value: string
  onChange: (value: string) => Promise<void>
}
export const NumberInput = ({value, onChange}: Props) => {
  const _onChange: React.ChangeEventHandler<HTMLInputElement> = event => {
    onChange(event.target.value)
  }

  return (
    <input
      type="number"
      value={value}
      onChange={onChange}
    />
  )
)

結論

string型として扱うほうが事故が発生しにくい。

詳細

number型として扱う場合のメリデメは以下のようになると考えている。

  • メリット
    • input[type=number]の時点で、最終的にnumberとして扱いたい物であることがほとんど。なので、いちいちキャストせずnumberのまま扱えるので楽。
  • デメリット
    • 空入力の際の扱いをどうするのか問題
      • valueAsNumberで取得するとNaNが返ってくる。NaNは当然様々な問題を引き起こす。
      • Number(event.target.value)で取得するという方法もあるが、そうなると「空入力を0として扱っていいの?」という問題が出る。空入力を0としていいかエラーとすべきかは使う状況によって変わってくるのでは?
      • そもそも空入力にならないよう制御するという対応

ともかく空入力の扱いがかなり難しい。ユーザーが空入力をした際に0として扱って問題ないのかもしくは何か別の対応をすべきなのかは状況によって変わるので、汎用コンポーネント側で一律制御するのは危険だと思う。

  • 汎用コンポーネントはvalueをstringで扱うようにする。
    • その際、汎用コンポーネントとして扱いやすいよう Omit<React.ComponentPropsWithRef<'input'>forwardRef を使用してinputタグと同様に扱えるようにしておいたほうが良い。
  • 実際の具体的な状況下において、stringである入力値をどう対処するか判断
    • 空入力を0として扱っても問題ないのであれば、Numberでキャストしたり、onChangeでnumberを返す制御コンポーネントを作成する等。

まとめ

valueAsNumber の存在を最近始めて知ってめっちゃ便利じゃん!って思ったら色々考慮した結果「使い所ないなこれ…」ってなった。