Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
592
Help us understand the problem. What is going on with this article?
@Yametaro

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

新しい記事もよろしくやで!
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、めっちゃ遅いね!」

592
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Yametaro
関西型言語が得意です。
yumemi
みんなが知ってるあのサービス、実はゆめみが作ってます。スマホアプリ/Webサービスの企画・UX/UI設計、開発運用。Swift, Kotlin, PHP, Vue.js, React.js, Node.js, AWS等エンジニア・クリエイターの会社です。Twitterで情報配信中https://twitter.com/yumemiinc

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
592
Help us understand the problem. What is going on with this article?