Reactの基礎 - PropsとChildren

提供: MochiuWiki : SUSE, EC, PCB

概要

Propsは、Reactにおけるコンポーネント間のデータ通信メカニズムである。
親コンポーネントから子コンポーネントへ一方向にデータを渡すことができ、これを 単方向データフロー と呼ぶ。

Propsには、オブジェクト、配列、関数、文字列や数値等のプリミティブ値といった任意のTypeScript値を渡すことができる。
渡されたPropsは読み取り専用 (イミュータブル) であり、子コンポーネントから直接変更することはできない。

主な特徴は以下の通りである。

  • 親コンポーネントから子コンポーネントへ一方向にデータが流れる
  • 任意のTypeScript / JavaScript値 (オブジェクト、配列、関数、プリミティブ) を渡せる
  • Propsは読み取り専用であり、受け取ったコンポーネントが変更することはできない
  • 分割代入やデフォルト値の指定により、簡潔に記述できる
  • TypeScriptと組み合わせることで、型安全なコンポーネント設計が可能になる



Propsの基本

Propsの受け渡し

親コンポーネントは、JSXの属性としてPropsを子コンポーネントへ渡す。
データは常に親から子へ一方向に流れる。(単方向データフロー)

以下の例では、Profile コンポーネントが Avatar コンポーネントへ personsize を渡している。

 export default function Profile() {
    return (
       <Avatar
          person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
          size={100}
       />
    );
 }


オブジェクトは二重中括弧 {{}} で、数値は単一の中括弧 {} で渡す琴に注意する。

Propsの受け取り

子コンポーネントは、関数の引数として props オブジェクトを受け取る。

 interface AvatarProps {
    person: { name: string; imageId: string };
    size: number;
 }
 
 function Avatar(props: AvatarProps) {
    let person = props.person;
    let size = props.size;
    // person と size を使用する処理
 }


この方法では、props.<プロパティ名> の形式でアクセスする必要がある。
より簡潔に記述するためには、#分割代入によるPropsの受け取り 次のセクションで説明する分割代入を使用する。


分割代入によるPropsの受け取り

分割代入の基本

関数の引数で分割代入を使用すると、Propsを直接変数として受け取ることができる。

 // AvatarProps型は上記で定義済み
 function Avatar({ person, size }: AvatarProps) {
    // personとsizeが直接利用可能
 }


props.person と記述する代わりに person と直接参照でき、ソースコードが簡潔になる。

残余プロパティ

スプレッド構文 ...rest を使用すると、指定したProps以外の残りのPropsをまとめて受け取ることができる。

 interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
    variant: string;
 }
 
 function Button({ variant, ...rest }: ButtonProps) {
    return <button className={variant} {...rest} />;
 }


  • variant
    取り出して使用するProp

  • ...rest
    variant以外の全てのPropsをオブジェクトとしてまとめたもの
    {...rest} で子要素にそのまま展開して渡すことができる



デフォルト値

分割代入によるデフォルト値

分割代入の際に = を使用して、Propが渡されなかった場合のデフォルト値を指定できる。

 interface AvatarProps {
    person: { name: string; imageId: string };
    size?: number;
 }

 function Avatar({ person, size = 100 }: AvatarProps) {
    // sizeが指定されない場合、または undefined の場合、100がデフォルト値として使用される
 }


デフォルト値が適用される条件と適用されない条件を以下に示す。

デフォルト値の適用条件
渡される値 デフォルト値の適用
指定なし (Propが存在しない) 適用される
undefined 適用される
null 適用されない (nullのまま)
0 適用されない (0のまま)
false 適用されない (falseのまま)



children

childrenの基本

JSXタグ内にネストされたコンテンツは、自動的に children Propとして子コンポーネントへ渡される。

 interface CardProps {
    children: React.ReactNode;
 }
 
 function Card({ children }: CardProps) {
    return (
       <div className="card">
          {children}
       </div>
    );
 }
 
 export default function Profile() {
    return (
       <Card>
          <Avatar />
       </Card>
    );
 }


Card コンポーネントは、タグ内にどのような要素が渡されるかを知らなくてもよい。
{children} の位置にネストされたコンテンツが展開される。

childrenの活用パターン

children を活用することにより、汎用的なラッパーコンポーネントを作成できる。

  • Cardコンポーネントの例
     interface CardProps {
        children: React.ReactNode;
        className?: string;
     }
     
     function Card({ children, className }: CardProps) {
        return (
           <div className={`card ${className}`}>
              {children}
           </div>
        );
     }
    

  • 複数のセクションを持つLayoutコンポーネントの例
    この場合、sidebarcontent のように、Propとして複数のJSX要素を渡す方法も有効である。
     interface LayoutProps {
        sidebar: React.ReactNode;
        content: React.ReactNode;
     }
     
     function Layout({ sidebar, content }: LayoutProps) {
        return (
           <div className="layout">
              <aside>{sidebar}</aside>
              <main>{content}</main>
           </div>
        );
     }
     
     <Layout
        sidebar={<Navigation />}
        content={<MainContent />}
     />
    

  • Modalコンポーネントの例
    isOpen がfalseの場合は、何もレンダリングしない設計にすることで、表示制御を親コンポーネント側で行える。
     interface ModalProps {
        isOpen: boolean;
        title: string;
        children: React.ReactNode;
        onClose: () => void;
     }
     
     function Modal({ isOpen, title, children, onClose }: ModalProps) {
        if (!isOpen) return null;
        return (
           <div className="modal-overlay">
              <div className="modal">
                 <h2>{title}</h2>
                 {children}
                 <button onClick={onClose}>Close</button>
              </div>
           </div>
        );
     }
    



スプレッド構文によるPropsの展開

スプレッド構文を使用すると、Propsをまとめて子コンポーネントへ渡すことができる。

以下の2つの記述は同じ動作をする。

 interface ProfileProps {
    person: { name: string; imageId: string };
    size: number;
    isSepia: boolean;
    thickBorder: boolean;
 }
 
 // 冗長な書き方
 function Profile({ person, size, isSepia, thickBorder }: ProfileProps) {
    return (
       <Avatar
          person={person}
          size={size}
          isSepia={isSepia}
          thickBorder={thickBorder}
       />
    );
 }
 
 // スプレッド構文 (簡潔な記述)
 function Profile(props: ProfileProps) {
    return <Avatar {...props} />;
 }


スプレッド構文は簡潔である一方、どのPropsが渡されるかが不明確になる場合がある。
そのため、使用は最小限にとどめ、Propsを個別に列挙する方が可読性は高い。

全てのコンポーネントでスプレッド構文を使用することは避けること。


Props型定義 (TypeScript)

interfaceによるProps型の定義

TypeScriptでは、interface を使用してPropsの型を定義することができる。
型定義により、コンパイル時に型の不一致を検出でき、IDEの補完機能も有効になる。

 interface MyButtonProps {
    title: string;
    disabled: boolean;
 }
 
 function MyButton({ title, disabled }: MyButtonProps) {
    return <button disabled={disabled}>{title}</button>;
 }


オプショナルPropsと必須Props

? を付加することでオプショナル (省略可能) なPropsを定義できる。
? のないPropsは必須となり、渡されない場合はコンパイルエラーになる。

 interface CardProps {
    title: string;         // 必須
    description?: string;  // オプショナル
    onClick?: () => void;  // オプショナルなコールバック
 }


childrenの型定義

children Propの型には React.ReactNode を使用することが推奨される。

 interface ModalRendererProps {
    title: string;
    children: React.ReactNode;
 }


children の型の選択肢を以下に示す。

childrenの型定義
説明
React.ReactNode(推奨) 文字列、数値、JSX 要素、null、undefined、配列等全ての値に対応する。
最も汎用的であり、通常はこちらを使用する。
React.ReactElement JSX要素のみを受け付ける、より限定的な型
文字列や数値を受け付けないため、必要な場合にのみ使用する。


イベントハンドラの型

Reactは各DOMイベントに対応する型を提供している。

 interface InputFieldProps {
    value: string;
    onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
 }


代表的なイベントハンドラの型を以下に示す。

代表的なイベントハンドラの型
対応するイベント
React.ChangeEvent<HTMLInputElement> 入力フィールドの値変更
React.MouseEvent<HTMLButtonElement> ボタンのクリック
React.FormEvent<HTMLFormElement> フォームの送信
React.KeyboardEvent<HTMLInputElement> キーボード入力
React.FocusEvent<HTMLInputElement> フォーカスの変更



Propsの設計原則

単方向データフロー

Reactでは、データは親コンポーネントから子コンポーネントへ一方向に流れる。
子コンポーネントが親の状態を直接変更することはできない。

子から親へデータを伝える場合は、コールバック関数をPropsとして渡す方法を使用する。
以下の例では、setFilterText 関数を onFilterTextChange Propとして渡すことで、子コンポーネントが親の状態を更新できる。

 interface SearchBarProps {
    filterText: string;
    onFilterTextChange: (text: string) => void;
 }
 
 function Parent() {
    const [filterText, setFilterText] = useState<string>('');
    return (
       <SearchBar
          filterText={filterText}
          onFilterTextChange={setFilterText}
       />
    );
 }
 
 function SearchBar({ filterText, onFilterTextChange }: SearchBarProps) {
    return (
       <input
          value={filterText}
          onChange={(e) => onFilterTextChange(e.target.value)}
       />
    );
 }


この設計パターンにより、状態の変更箇所が明確になり、データの流れを追いやすくなる。

Propsの設計における主な原則を以下に示す。

Props 設計のベストプラクティス
項目 説明
データは親から子へ一方向に渡す 子コンポーネントは受け取ったPropsを直接変更しない。
子から親への通信はコールバック関数で行う onChangeonCloseonSubmit 等の命名規則を使用する。
Propsは最小限にする 不要なPropsを渡さないことで、コンポーネントの責務を明確にする。
TypeScriptで型を定義する 型定義により、意図しない使用を防ぎ、ソースコードの可読性を高める。



関連情報