[React] Input propsを省略して書く

April 27, 2021

programming
article cover

Presentational VS Container

Reactのベストプラクティスとして、コンポーネントを2種類に分けるのが一般的かと思います。 ざっくりと説明すると以下のようになりますよね。

Presentational component

  • 渡されたpropsを表示する
  • HTMLやスタイルを記述する
  • Fluxフローなどに依存しない
  • データを読み込んだり、変更させない
  • statelessである

Container component

  • 子孫コンポーネントを統括する
  • HTMLやスタイルは関与しない/記述しない
  • ReactのFluxフローに直接関与する
  • 外部APIなどと通信する
  • statefulである

この構造がReactのシンプルさと力強さをより一層支えているわけですが、その分どうしてもpropsの受け渡しやリレーは増えていくことになります。

今回はInputに渡すpropsを省略する方法を残しておきます。

フォームを作る

例としてサインアップフォームを作ると想定しましょう。

input.tsxを作って

import React from 'react'

interface Props {
  labelText: string
  type: string
  name: string
}

export const Input = (props: Props) => {
  const { labelText, type, name } = props

  return (
    <label>
      <span>{labelText}</span>
      <input type={type} name={name} />
    </label>
  )
}

form.tsxでimportしてrenderします。

import React from 'react'
import { Input } from './input'

export const Form = () => {
  return (
    <div>
      <h1>Sign up form</h1>
      <Input labelText={'First name'} type={'text'} name={'firstName'} /> 
      <Input labelText={'Last name'} type={'text'} name={'lastName'} /> 
      <Input labelText={'email'} type={'email'} name={'email'} /> 
      <Input labelText={'password'} type={'password'} name={'password'} />
    </div>
  );
}

form

More props

これだけなら良いのですが、実際はもっとたくさんのpropsが必要になってきますね。

import React from 'react'

interface Props {
  labelText: string
  type: string
  name: string
  placeholder: string
  disabled: boolean
  required: boolean
  onChange: React.ChangeEventHandler<HTMLInputElement>
}

export const Input = (props: Props) => {
  const { 
    labelText, 
    type, 
    name, 
    placeholder,
    disabled,
    required,
    onChange
  } = props

  return (
    <label>
      <span>{labelText}</span>
      <input 
        type={type} 
        name={name} 
        placeholder={placeholder}
        disabled={disabled}
        required={required}
        onChange={onChange}
      />
    </label>
  )
}

例としてこれだけ足してみましたが、実際のコードだともっとたくさん指定することになると思います。 かなりゴチャゴチャしてくるので、スッキリさせましょう。
今回使うのはコイツです。

JSX.IntrinsicElements['input']

これでinput要素に適応できる属性の一覧が取得できるので、Propsの定義がスッキリします。

import React from 'react'

type InputProps = JSX.IntrinsicElements['input']
type Props = InputProps & { labelText: string }

export const Input = (props: Props) => {
  const { 
    labelText, 
    type, 
    name, 
    placeholder,
    disabled,
    required,
    onChange
  } = props

  return (
    <label>
      <span>{labelText}</span>
      <input 
        type={type} 
        name={name} 
        placeholder={placeholder}
        disabled={disabled}
        required={required}
        onChange={onChange}
      />
    </label>
  )
}

まだまだ省略できます。
スプレッド構文を使ってpropsを<input>に一気に渡せます。

import React from 'react'

type InputProps = JSX.IntrinsicElements['input']
type Props = InputProps & { labelText: string }

export const Input = (props: Props) => {
  return (
    <label>
      <span>{props.labelText}</span>
      <input {...props} />
    </label>
  )
}

かなりスッキリしましたが、もう一つだけやることがあります。

warning

<input>はlabelTextという属性を持っていないので、それについて怒られています。
labelTextだけをpropsから抜きっておきましょう。
これも同じくスプレッド構文で簡単にできます。

import React from 'react'

type InputProps = JSX.IntrinsicElements['input']
type Props = InputProps & { labelText: string }

export const Input = (props: Props) => {
  const { labelText, ...inputProps} = props
  
  return (
    <label>
      <span>{labelText}</span>
      <input {...inputProps} />
    </label>
  )
}

これで完了です。限りなくシンプルになりました。
スプレッド構文をうまく使えば、<input>用以外のpropsが増えても綺麗に書くことができます。


Profile picture

元カメラマン。今はベルリンで働くWEBエンジニア。
スロバキア人の妻とドイツ猫二匹の多国籍家族。