1166
736

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-04-11

新しい記事もよろしくやで!
5歳娘「パパのReact、めっちゃ遅いね!」

とある平日

娘(5歳)「パパ、今日は何のお仕事してるの?」

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

今日のお仕事内容

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

スクリーンショット

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

ワイ「せやせや」

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

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

スクリーンショット

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

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

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

スクリーンショット

ワイ「↑これだけや」

/pages/form.tsx
  import React from 'react'

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

  export default FormPage

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

娘「なるほどね」

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

コンポーネント作り開始

ワイ「まずは・・・」

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

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

/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

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

スクリーンショット

ワイ「↑こうやな」

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

ワイ「おお」

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

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

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

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

まずはpropsの型を書く

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

/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を」
ワイ「子コンポーネントの中で表示せなあかんから・・・」

/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を渡すの?」

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

/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

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

スクリーンショット

ワイ「↑こうやな」

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

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

娘「そうなの?」

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

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

娘「確かに」

ワイ「せやから、MDNのページとかを見ながら」
ワイ「input要素に必要そうなpropsを全部書いていくんや・・・!!!」

/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要素の属性一覧を、一生懸命ネットで調べるのもいいんだけどさ」

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

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

ワイ「ファーーー・・・(失神)」

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

/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はね、スプレッド構文を使うことで・・・」

/components/LabeledInput.tsx
  <input {...props} />

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

ワイ「おお・・・」

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

/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要素に渡したいわけ」

ワイ「そのやり方を教えてください(観念)」

娘「はい」

/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>
    )
  }

娘「↑こうだね」

ワイ「おお、なるほど」

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

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

娘「そう」
娘「こないだ@suinさんが似た感じのことをやってたのを、さっき思い出したの」

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

最終的に

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

/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のやり過ぎか!!!」

〜おしまい〜

余談

新しい記事もよろしくやで!

5歳娘「パパのReact、めっちゃ遅いね!」

1166
736
11

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1166
736

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?