新しい記事もよろしくやで!
5歳娘「パパのReact、めっちゃ遅いね!」
とある平日
娘(5歳)「パパ、今日は何のお仕事してるの?」
ワイ「おお、娘ちゃん」
ワイ「今日はな、ショッピングサイトを作ってんのや」
今日のお仕事内容
ワイ「↓このデザインの通りに、コーディングをせなあかんのや」
娘「なるほどー」
娘「このショッピングサイトで商品を売りたい!っていうお店があったとして」
娘「そのお店の人が、最初にお店の情報を登録するためのページだね!」
ワイ「せやせや」
まずはデザインを眺めてみる
ワイ「この店舗登録ページにはなぁ」
ワイ「↑こんな感じの」
ワイ「項目名と入力欄がセットになったパーツが何度も登場するから」
ワイ「そのためのコンポーネントを作ろうかなー、って」
ワイ「そう思ってたとこなんや」
娘「ふーん」
娘「ラベル付きテキストフィールド的なコンポーネントってことだね」
娘「どこまで作ったの?」
ワイ「まだ、ページ側のファイルを作っただけや」
ワイ「↑これだけや」
import React from 'react'
const FormPage: React.FC = () => {
return (
<>
<h1>店舗登録</h1>
<p>店舗情報を入力してください。</p>
</>
)
}
export default FormPage
ワイ「↑コードもまだこんだけや」
娘「なるほどね」
ワイ「ほな今から、ラベル付きテキストフィールドのコンポーネントを作っていくで!」
コンポーネント作り開始
ワイ「まずは・・・」
export const LabeledInput: React.FC<Props> = (props) => {
return (
<label>
<span>項目名</span>
<input type="text" name="項目名" />
</label>
)
}
ワイ「↑こんな感じや」
ワイ「これをページ側で呼び出すには・・・」
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
ってのも定義しておこか」
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
を」
ワイ「子コンポーネントの中で表示せなあかんから・・・」
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
を渡すの?」
ワイ「ページの方は・・・」
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
を全部書いていくんや・・・!!!」
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
要素の属性一覧を、一生懸命ネットで調べるのもいいんだけどさ」
type InputProps = JSX.IntrinsicElements['input']
娘「↑これだけでいいんだよ」
ワイ「ファーーー・・・(失神)」
娘「これだけで、input
要素の属性一覧の型情報を取得できるの」
娘「つまり、props
の型定義の部分は」
- 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
はね、スプレッド構文を使うことで・・・」
<input {...props} />
娘「↑こんな感じで一気に渡せるの」
ワイ「おお・・・」
娘「つまり、さっきのコンポーネントのコードは・・・」
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要素に渡したいわけ」
ワイ「そのやり方を教えてください(観念)」
娘「はい」
export const LabeledInput: React.FC<Props> = (props) => {
- const { labelText } = props
+ const { labelText, ...inputProps } = props
return (
<label>
<span>{labelText}</span>
- <input {...props} />
+ <input {...inputProps} />
</label>
)
}
娘「↑こうだね」
ワイ「おお、なるほど」
const { labelText, ...inputProps } = props
ワイ「↑こう、分割代入と残余構文を使うことで」
ワイ「labelText
とそれ以外に分割してやるんか」
娘「そう」
娘「こないだ@suinさんが似た感じのことをやってたのを、さっき思い出したの」
ワイ「おお、いつもお世話になっております・・・」
最終的に
娘「ってことで、パパの書いたコンポーネントのコードは」
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のやり過ぎか!!!」
〜おしまい〜
余談