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>
);
}
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>
)
}
かなりスッキリしましたが、もう一つだけやることがあります。
<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が増えても綺麗に書くことができます。