こんにちは!Reactの学習、楽しく進められていますか?
画面にボタンを並べて、文字を表示させて……「お、いい感じに動くぞ!」と感動したあとに、必ずと言っていいほどぶつかる壁があります。
それが、「コンポーネント同士でデータをどうやってやり取りするの?」、そして今回のテーマである「Controlled(制御された)と Uncontrolled(制御されていない)コンポーネントのProps設計」というちょっぴり難しそうな概念です。
名前を聞くだけで「うっ……」と身構えてしまうかもしれませんが、大丈夫ですよ。
実はこれ、私たちの身の回りにある「家電製品のスイッチ」や「お買い物のレジ」と同じくらい、とってもシンプルで温かみのある仕組みなんです。
今日は、難しい専門用語はいったん脇に置いて、一緒にゆっくりと紐解いていきましょう!
—
そもそも「Controlled」と「Uncontrolled」ってなに?
Reactでフォームの入力欄(インプット要素)などを扱うとき、私たちはよく「値の管理をどこに任せるか?」という選択を迫られます。
これを身近なものに例えてみましょう。
1. Uncontrolled(制御されていない)=「昔ながらの扇風機」
ボタンをポチッと押すと、扇風機自身が「よし、今の俺の風量は『中』だな」と内部で記憶して動くタイプです。
外から「今、風量いくつ?」と聞かないと分からないし、外から強制的に「今すぐ『弱』になりなさい!」と命令するのは少し面倒です。
Reactでいうと、`ref`を使ってDOMから直接今の値を取り出すようなイメージですね。コードは短く書けますが、親から中の様子が見えにくくなります。
2. Controlled(制御された)=「リモコン付きのエアコン」
今の状態(室温や風量)は、すべて手元にある「リモコン(親コンポーネント)」が完璧に把握しています。
エアコン本体は、リモコンから「はい、今は26度にしてください」「風量変えましたよ」と言われた命令を忠実に実行するだけ。
Reactのフォームで言うと、`value`(今の値)と `onChange`(値が変わったよと伝える合図)をセットで渡して、親がすべての主導権を握るスタイルです。
実務の現場では、入力内容のバリデーション(エラーチェック)をしたり、送信ボタンの有効・無効を切り替えたりするため、この「Controlled(制御された)」スタイルが好まれることが多いです。
—
プロの現場でよくある悩み:「どっちつかずのゾンビ」を作っていませんか?
初学者の頃、よくやってしまうのがこんなコードです。
「とりあえず初期値を入れておこう」と、中に`useState`を持たせたはいいものの、親からも`value`を渡されて……どっちの言うことを聞けばいいのか分からない、いわば「どっちつかずの迷子コンポーネント」が生まれてしまうことがあります。
これでは、親が「変えて!」と言っているのに子が「いや、私の中ではこうなんです!」と聞き入れず、画面の表示がバグる原因になってしまいます。
プロの現場で求められるのは、「外からコントロールもできるし、いざとなったら自分で自立して動くこともできる、懐の深いハイブリッドな設計」です。
—
実践!「value / onChange」を受け入れる優しいProps設計
それでは、実際にコードを書いてみましょう!
今回は、私たちが毎日使うような「文字を入力するインプット欄(のカスタム版)」を例にします。
ここでは、「親から値が渡されてきたらそれに従う(Controlled)」、もし「渡されなくても、自分の中に状態を持って動ける(Uncontrolled)」という、いいとこ取りをしたコンポーネントを作ってみます。
import React, { useState } from ‘react’;
/
- 親でも子でも優しく扱える、ハイブリッドな入力コンポーネント
/
function FriendlyInput({ value: externalValue, onChange, defaultValue = ” }) {
// 1. 親から value が渡されているかどうかで、制御されているかを判断します
const isControlled = externalValue !== undefined;
// 2. 親から値が渡されない(Uncontrolledな)場合のための、自分用の隠しポケット(内部state)
const [internalValue, setInternalValue] = useState(defaultValue);
// 3. 実際に画面で表示する値は、「親にもらったもの」か「自分で持っているもの」のどちらか!
const currentValue = isControlled ? externalValue : internalValue;
// 4. 文字が入力されたときの処理
const handleChange = (e) => {
const newValue = e.target.value;
// もし親から「onChangeで教えてね」と頼まれているなら、親に報告する
if (onChange) {
onChange(newValue);
}
// もし自分自身で状態を管理するモードなら、自分のポケットの中身も更新する
if (!isControlled) {
setInternalValue(newValue);
}
};
return (
今の状態: {isControlled ? ‘🔒 親に制御されています (Controlled)’ : ‘🌱 自分で自由に動いています (Uncontrolled)’}
);
}
export default FriendlyInput;
このコードの「愛すべきポイント」
1. `isControlled` という判定の魔法
`externalValue !== undefined` という一行で、「あ、親御さんがこの子に手を繋いであげているな(Controlledだ)」ということが一目で分かります。
2. 親が手放しても泣かない設計
もし親が `value` を渡すのを忘れても、`defaultValue` や内部の `internalValue` がそっとサポートしてくれるため、エラーで画面が真っ白になるような悲劇を防げます。
—
親から使ってみる:お買い物のレジをイメージして
この `FriendlyInput` を、親コンポーネントから実際に使ってみましょう。
ここでは、親がしっかりと値を持って管理する「Controlled」な使い方をしてみます。
import React, { useState } from ‘react’;
import FriendlyInput from ‘./FriendlyInput’;
function ShoppingCounter() {
// 親がクーポンコードの「本当の状態」をがっちり握っています
const [couponCode, setCouponCode] = useState(”);
return (
お支払い画面
親が管理している現在のクーポン: {couponCode || ‘未入力’}
{/ 親が value と onChange を渡して、完全にコントロールしています /}
/>
);
}
export default ShoppingCounter;
—
さいごに:つまずいても、焦らなくて大丈夫
ReactのProps設計、特にControlledとUncontrolledの境界線は、最初は頭がこんがらがって当たり前です。「あれ、なんで値が消えちゃうんだ?」「なんで入力できないんだ?」と何回もエラー画面と睨めっこするのが、一人前のエンジニアへの第一歩です。
大切なのは、コンポーネントを作る時に「この子は親に全部お世話してもらうタイプ(Controlled)にするかな? それとも自分で元気に動くタイプ(Uncontrolled)にするかな?」と、優しく設計図を描いてあげること。
あなたが書いたそのコードは、きっと使うユーザーにとっても、一緒に働くチームメイトにとっても、温かく使いやすいものになるはずです。
今日の学びが、あなたのReactライフを少しでも楽しく、軽やかにするスパイスになりますように。
それでは、また次の冒険でお会いしましょう!

コメント