5歳娘「パパ、余分なpropsいっぱい書くんだね！」

新しい記事もよろしくやで！
[**5歳娘「パパのReact、めっちゃ遅いね！」**](https://qiita.com/Yametaro/items/621bfd8c894f8fd36539)

## とある平日
娘（**5歳**）「パパ、今日は何のお仕事してるの？」

ワイ「おお、娘ちゃん」
ワイ「今日はな、**ショッピングサイト**を作ってんのや」

## 今日のお仕事内容

ワイ「↓このデザインの通りに、コーディングをせなあかんのや」

<img width="480" alt="スクリーンショット" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/2d7d3ee4-5e1e-0ecb-0f58-5cb239ef1db7.png">

娘「なるほどー」
娘「このショッピングサイトで商品を売りたい！っていうお店があったとして」
娘「そのお店の人が、最初にお店の情報を登録するためのページだね！」

ワイ「せやせや」

## まずはデザインを眺めてみる

ワイ「この**店舗登録**ページにはなぁ」

<img width="480" alt="スクリーンショット" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/88634b7d-e6f3-2f27-b319-443f32a3861f.png">

ワイ「↑こんな感じの」
ワイ「**項目名**と**入力欄**がセットになったパーツが何度も登場するから」
ワイ「そのための**コンポーネント**を作ろうかなー、って」
ワイ「そう思ってたとこなんや」

娘「ふーん」
娘「**ラベル付きテキストフィールド**的なコンポーネントってことだね」
娘「どこまで作ったの？」

ワイ「まだ、ページ側のファイルを作っただけや」

<img width="480" alt="スクリーンショット" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/2e4117ad-7026-3ce1-2ae6-c95b47a76597.png">

ワイ「↑これだけや」

```diff_tsx:/pages/form.tsx
  import React from 'react'

  const FormPage: React.FC = () => {
    return (
      <>
        <h1>店舗登録</h1>
        <p>店舗情報を入力してください。</p>
      </>
    )
  }

  export default FormPage
```

ワイ「↑コードもまだこんだけや」

娘「なるほどね」

ワイ「ほな今から、**ラベル付きテキストフィールド**のコンポーネントを作っていくで！」

## コンポーネント作り開始

ワイ「まずは・・・」

```diff_tsx:/components/LabeledInput.tsx
  export const LabeledInput: React.FC<Props> = (props) => {
    return (
      <label>
        <span>項目名</span>
        <input type="text" name="項目名" />
      </label>
    )
  }
```

ワイ「↑こんな感じや」
ワイ「これをページ側で呼び出すには・・・」

```diff_tsx:/pages/form.tsx
  import React from 'react'
+ import { LabeledInput } from 'src/components/LabeledInput'

  const FormPage: React.FC = () => {
    return (
      <>
        <h1>店舗登録</h1>
        <p>店舗情報を入力してください。</p>
+       <LabeledInput />
+       <LabeledInput />
+       <LabeledInput />
+       <LabeledInput />
+       <LabeledInput />
      </>
    )
  }

  export default FormPage
```

ワイ「↑こうやな」
ワイ「こうすると、画面の方は・・・」

<img width="480" alt="スクリーンショット" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/251b57c4-2069-c791-8e3e-34f9ce64845f.png">


ワイ「↑こうやな」

娘「わー、ちゃんと表示されたね」

ワイ「おお」

娘「でも、ラベル部分が全部`項目名`になっちゃってるよ？」

ワイ「大丈夫や」
ワイ「初期リリース時はその要件でOKなはずや」

娘「そんなわけないから`props`書こうよ」

ワイ「せやな」
ワイ「ワイが**会社からリリース**されてしまうところやったわ」

## まずは`props`の型を書く

ワイ「ほんなら」
ワイ「input要素に渡す`type`とか`name`と」
ワイ「あと、ラベル文字列も`props`で渡したいから」
ワイ「`labelText`ってのも定義しておこか」

```diff_tsx:/components/LabeledInput.tsx
  import React from 'react'
 
- type Props = {}
+ type Props = {
+   type: string
+   name: string
+   labelText: string
+ }
  
  export const LabeledInput: React.FC<Props> = (props) => {
    return (
      <label>
        <span>項目名</span>
        <input type="text" name="項目名" />
      </label>
    )
  }
```

ワイ「↑`props`の型はこんな感じやな」
ワイ「そんで、親コンポーネントからもらってきた`props`を」
ワイ「子コンポーネントの中で表示せなあかんから・・・」

```diff_tsx:/components/LabeledInput.tsx
  import React from 'react'

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

  export const LabeledInput: React.FC<Props> = (props) => {
+   const { type, name, labelText } = props
+
    return (
      <label>
-       <span>項目名</span>
-       <input type="text" name="項目名" />
+       <span>{labelText}</span>
+       <input type={type} name={name} />
      </label>
    )
  }
```

ワイ「↑こうやな！」

娘「なるほど〜」
娘「ページの方からは、このコンポーネントにどうやって`props`を渡すの？」

ワイ「ページの方は・・・」

```diff_tsx:/pages/form.tsx
  import React from 'react'
  import { LabeledInput } from 'src/components/LabeledInput'

  const FormPage: React.FC = () => {
    return (
      <>
        <h1>店舗登録</h1>
        <p>店舗情報を入力してください。</p>
-       <LabeledInput />
-       <LabeledInput />
-       <LabeledInput />
-       <LabeledInput />
-       <LabeledInput />
+       <LabeledInput type="text" name="shopName" labelText="店舗名" />
+       <LabeledInput type="text" name="repName" labelText="代表者名" />
+       <LabeledInput type="text" name="repName" labelText="業種" />
+       <LabeledInput type="text" name="repName" labelText="郵便番号" />
+       <LabeledInput type="text" name="repName" labelText="住所" />
      </>
    )
  }

  export default FormPage
```

ワイ「↑こんな感じや」
ワイ「こうすると画面は・・・」

<img width="480" alt="スクリーンショット" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295452/403bd0d2-826e-cb65-1e84-386d680c3189.png">


ワイ「↑こうやな」

娘「わーい！ほぼ完成だね！」

ワイ「いや、全然まだまだやで」

娘「そうなの？」

## `props`をどんどん追加していく

ワイ「input要素に`disabled`属性をつけたり」
ワイ「`onClick`属性としてイベントハンドラ関数を渡したり」
ワイ「そういう`props`がまだまだ全然足りてへん」

娘「確かに」

ワイ「せやから、[MDNのページ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/input/text)とかを見ながら」
ワイ「input要素に必要そうな`props`を全部書いていくんや・・・！！！」

```diff_tsx:/components/LabeledInput.tsx
  import React from 'react'
  
  type Props = {
    type: string
    name: string
+   placeholder: string
+   autoComplete: string
+   disabled: boolean
+   required: boolean
+   minLength: number
+   maxLength: number
+   onChange: React.ChangeEventHandler<HTMLInputElement>
+   onBlur: React.FocusEventHandler<HTMLInputElement>
    labelText: string
  }

  export const LabeledInput: React.FC<Props> = (props) => {
-   const { type, name, labelText } = props
+   const {
+     type,
+     name,
+     value,
+     placeholder,
+     autoComplete,
+     disabled,
+     required,
+     minLength,
+     maxLength,
+     onChange,
+     onBlur,
+     labelText,
+   } = props

    return (
      <label>
        <span>{labelText}</span>
-       <input type={type} name={name} />
+       <input
+         type={type}
+         name={name}
+         value={value}
+         placeholder={placeholder}
+         autoComplete={autoComplete}
+         disabled={disabled}
+         required={required}
+         minLength={minLength}
+         maxLength={maxLength}
+         onChange={onChange}
+         onBlur={onBlur}
+       />
      </label>
    )
  }

```

ワイ「↑こんなもんや！！！！」
ワイ「（ドヤァ・・・・）」
ワイ「（パパはいつも、こんな大変なお仕事をしてるんやでぇ・・・！？）」

#### 5歳娘「パパ、余分なpropsいっぱい書くんだね！」

ワイ「**ファッ！？**」

## 5歳児の書き方

娘「パパ」
娘「`input`要素の属性一覧を、一生懸命ネットで調べるのもいいんだけどさ」

```diff_tsx:/components/LabeledInput.tsx
  type InputProps = JSX.IntrinsicElements['input']
```

娘「↑これだけでいいんだよ」

ワイ「ファーーー・・・（**失神**）」

娘「これだけで、`input`要素の属性一覧の型情報を取得できるの」
娘「つまり、`props`の型定義の部分は」

```diff_tsx:/components/LabeledInput.tsx
- type Props = {
-   type: string
-   name: string
-   placeholder: string
-   autoComplete: string
-   disabled: boolean
-   required: boolean
-   minLength: number
-   maxLength: number
-   onChange: React.ChangeEventHandler<HTMLInputElement>
-   onBlur: React.FocusEventHandler<HTMLInputElement>
-   labelText: string
- }
+ type InputProps = JSX.IntrinsicElements['input']
+ type Props = InputProps & { labelText: string }
```

娘「↑この**2行**で済むわけだね」

ワイ「ぐぬぬ・・・」
ワイ「あ、ありがとうやで娘ちゃん・・・」
ワイ「だいぶコードがスッキリしたわ・・・」

娘「え、まだまだ余分なところがいっぱいあるよ？」

ワイ「ファファファ・・・」

## `props`はスプレッド構文で渡せる

娘「`props`はね、スプレッド構文を使うことで・・・」

```diff_tsx:/components/LabeledInput.tsx
  <input {...props} />
```

娘「↑こんな感じで一気に渡せるの」

ワイ「おお・・・」

娘「つまり、さっきのコンポーネントのコードは・・・」

```diff_tsx:/components/LabeledInput.tsx
  export const LabeledInput: React.FC<Props> = (props) => {
-   const {
-     type,
-     name,
-     value,
-     placeholder,
-     autoComplete,
-     disabled,
-     required,
-     minLength,
-     maxLength,
-     onChange,
-     onBlur,
-     labelText,
-   } = props
+   const { labelText } = props

  return (
    <label>
      <span>{labelText}</span>
-       <input
-         type={type}
-         name={name}
-         value={value}
-         placeholder={placeholder}
-         autoComplete={autoComplete}
-         disabled={disabled}
-         required={required}
-         minLength={minLength}
-         maxLength={maxLength}
-         onChange={onChange}
-         onBlur={onBlur}
-       />
+       <input {...props} />
    </label>
  )
}
```

娘「↑こうまとめられちゃうね！」

ワイ「......シテ......コロシテ......」

## しかし、このままだと`labelText`もinput要素に渡ってしまう

娘「でもね、これだけだと」
娘「ページ側から渡された`labelText`っていう`props`も」
娘「まとめてそのままinput要素に渡されてしまうの」
娘「そうすると・・・」

> **Warning:** 
ReactはDOM要素の`labelText`というpropを認識しません。
意図的にカスタム属性としてDOMに表示させたい場合は、代わりに小文字の `labeltext` と綴ります。
**誤って親コンポーネントから渡してしまった場合は、DOM要素から削除してください。**

娘「↑こんな警告がコンソールに出ちゃうの」

ワイ「なるほどな」

娘「だから`labelText`と**それ以外**を分割して」
娘「必要なものだけinput要素に渡したいわけ」

ワイ「そのやり方を教えてください（**観念**）」

娘「はい」

```diff_tsx:/components/LabeledInput.tsx
  export const LabeledInput: React.FC<Props> = (props) => {
-   const { labelText } = props
+   const { labelText, ...inputProps } = props

    return (
      <label>
        <span>{labelText}</span>
-       <input {...props} />
+       <input {...inputProps} />
      </label>
    )
  }
```

娘「↑こうだね」

ワイ「おお、なるほど」

```diff_tsx:/components/LabeledInput.tsx
  const { labelText, ...inputProps } = props
```

ワイ「↑こう、分割代入と**残余構文**を使うことで」
ワイ「`labelText`と**それ以外**に分割してやるんか」

娘「そう」
娘「こないだ[@suinさんが似た感じのことをやってた](https://twitter.com/suin/status/1380489354783559682?s=20)のを、さっき思い出したの」

ワイ「おお、いつもお世話になっております・・・」

## 最終的に
娘「ってことで、パパの書いたコンポーネントのコードは」

```diff_tsx:/components/LabeledInput.tsx
  import React from 'react'

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

  export const LabeledInput: React.FC<Props> = (props) => {
    const { labelText, ...inputProps } = props

    return (
      <label>
        <span>{labelText}</span>
        <input {...inputProps} />
      </label>
    )
  }
```

娘「↑こんな感じに変わったね！」

ワイ「いや**ほぼ全部変わっとるやないかい！！！**」

## まとめ

- input要素の属性の型は、まとめて取得できる  
  → `JSX.IntrinsicElements['input']` 
- `props`を渡すとき、スプレッド構文を使って一気に書ける  
  → `<input {...props} />`
- `props`を**何か**と**それ以外**に分けたい場合は、分割代入＆**残余構文**で書く  
  → `const { labelText, ...inputProps } = props`

ワイ「ってことやな！」

娘「そうだね！」


## その日の夜
ワイ「娘ちゃんは凄いな〜」
ワイ「5歳なのにあんなコードが書けるなんて」
ワイ「もしかして、もう算数とかもできるんちゃうか？」

娘「できるよ！」
娘「パパ、問題出してみて！」

ワイ「ええで！」
ワイ「ほな行くで〜」
ワイ「`りんご1個` + `みかん1個`、合わせていくつ？」

娘「えぇ〜と・・・」
娘「わかった！」
娘「答えは、`"りんご1個みかん1個"`！」

ワイ「いや**文字列結合**！」
ワイ「**JavaScriptのやり過ぎ**か！！！」

〜おしまい〜

## 余談

https://twitter.com/yametaro1983/status/1381216225590534147?s=21

## 新しい記事もよろしくやで！
[**5歳娘「パパのReact、めっちゃ遅いね！」**](https://qiita.com/Yametaro/items/621bfd8c894f8fd36539)