【入門編】 Controlled/UncontrolledコンポーネントのProps設計 – React実践ガイド

こんにちは!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 を渡して、完全にコントロールしています /}
setCouponCode(newValue)}
/>

);
}

export default ShoppingCounter;

—

さいごに:つまずいても、焦らなくて大丈夫

ReactのProps設計、特にControlledとUncontrolledの境界線は、最初は頭がこんがらがって当たり前です。「あれ、なんで値が消えちゃうんだ?」「なんで入力できないんだ?」と何回もエラー画面と睨めっこするのが、一人前のエンジニアへの第一歩です。

大切なのは、コンポーネントを作る時に「この子は親に全部お世話してもらうタイプ(Controlled)にするかな? それとも自分で元気に動くタイプ(Uncontrolled)にするかな?」と、優しく設計図を描いてあげること。

あなたが書いたそのコードは、きっと使うユーザーにとっても、一緒に働くチームメイトにとっても、温かく使いやすいものになるはずです。

今日の学びが、あなたのReactライフを少しでも楽しく、軽やかにするスパイスになりますように。
それでは、また次の冒険でお会いしましょう!

コメント

タイトルとURLをコピーしました