Functional Component — 通常のJavaScript関数を使ってReactコンポーネントを作成する方法で、React 16.8のリリースとフックの登場により標準となりました。関数コンポーネントはpropsを受け取り、React.Componentからの継承を必要とせずにReact要素を返します。React、2024によると、Reactエコシステムの97%の新規コンポーネントが関数として書かれています。
重要ポイント
関数コンポーネントは、propsオブジェクトを受け取りReact要素を返す関数として定義されたReactコンポーネントです。React 16.8以前は、内部状態を管理できなかったため、これらのコンポーネントはステートレスと呼ばれていました。
React 16.8(2019年2月)でのフックの導入により、関数コンポーネントは状態(useState)、副作用(useEffect)、コンテキスト(useContext)、ref(useRef)にアクセスできるようになりました。「ステートレス」という用語は時代遅れです — 現代の関数コンポーネントは状態を完全に管理します。
Reactは公式に関数コンポーネントをコンポーネント作成の主要な方法として推奨しています。react.devのすべてのドキュメント、例、チュートリアル、新しいAPI(Server Components、Actions)は関数型アプローチに基づいています。クラスコンポーネントは削除されていませんが、Reactの新機能はフックを通じてのみ利用可能です。
最も簡単な関数コンポーネントは、JSXを返す関数です。propsを引数として受け取り、コンストラクター、super呼び出し、thisバインディングは不要です。propsは関数の引数を通じて直接アクセス可能です — this.propsは不要です。
// Functional component without state
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow function (common variant)
const Greeting = ({ name }) => (
<h1>Hello, {name}!</h1>
);
状態を追加するにはuseStateを使用します。フックは現在の状態とセッター関数の2つの値を持つタプルを返します。クラスベースのthis.setStateとは異なり、useStateは前の値とマージするのではなく、値を完全に置き換えます。
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
フックは現代のReactの基礎です。各フックは特定のタスクを解決します:useState — 状態、useEffect — 副作用、useContext — コンテキストアクセス、useRef — DOM要素参照、useCallbackとuseMemo — メモ化。
useEffectはcomponentDidMount、componentDidUpdate、componentWillUnmountを同時に置き換えます。最初の引数はエフェクトを持つコールバック、2番目は依存配列です。配列が空の場合、エフェクトはマウント後に1回実行されます。依存関係が指定されている場合、それらのいずれかが変更されるたびに実行されます。
import React, { useState, useEffect } from 'react';
function UserData({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (!cancelled) setUser(data);
});
// Cleanup - analog of componentWillUnmount
return () => { cancelled = true; };
}, [userId]); // Dependency
return <div>{user?.name}</div>;
}
関数コンポーネントの主な利点は、カスタムフックによるロジックの再利用です。クラスコンポーネントではロジックがcomponentDidMountとcomponentDidUpdateで重複していましたが、関数コンポーネントではuseプレフィックスを持つ別の関数に抽出されます。カスタムフックはHOCやrender propsに代わる主要な方法です。
Reactはコンポーネント間のコード再利用に継承よりも合成を推奨しています。関数コンポーネントは合成に理想的です:1つのコンポーネントが複数の他のコンポーネントを含み、propsとchildrenを渡せます。
childrenパターン — コンポーネントがchildrenプロップを通じて子要素を受け取る — は自然に実装されます:function Layout({ children })。Vueのスロットに相当するものは、名前付きコンポーネントプロップを通じて実装されます:function Page({ header, sidebar, content })。
継承はReactではほとんど使用されません。技術的にextendsをサポートするクラスコンポーネントでさえ、1階層より深い階層を形成することは稀です。合成を用いた関数コンポーネントは、継承なしでコード再利用のニーズを完全にカバーします。
関数コンポーネントには組み込みの最適化メカニズムが備わっています。React.memo — 関数用のPureComponent相当:propsが変更されていない場合に再レンダリングを防ぎます(浅い比較)。useMemoはレンダリング間で計算結果をキャッシュします。
import React, { useMemo } from 'react';
// React.memo - memoize component
const ExpensiveList = React.memo(({ items }) => {
return <ul>
{items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>;
});
// useMemo - caching result
function Dashboard({ transactions }) {
const summary = useMemo(() => {
return transactions.reduce((acc, t) => acc + t.amount, 0);
}, [transactions]);
return <p>Total: {summary}</p>;
}
useCallbackはコールバック関数をメモ化し、毎回のレンダリングで再作成されないようにします。これはReact.memoでラップされた子コンポーネントに関数を渡す際に重要です:useCallbackがないと、関数参照が変わるたびに子コンポーネントが再レンダリングされます。
アプローチの違いは構文を超えています。関数コンポーネントはクロージャです:状態と効果は特定のレンダリングに結びついています。クラスコンポーネントはthisを使用します:状態は常にthis.stateから取得され、非同期コールバックの実行時には古くなっている可能性があります。
| 側面 | 関数型 | クラス型 |
|---|---|---|
| 状態 | useState(独立) | this.state + this.setState |
| 効果 | 依存関係付きuseEffect | componentDidMount + componentDidUpdate |
| コンテキスト | useContext | Context.Consumerまたはstatic contextType |
| 最適化 | React.memo + useMemo | PureComponent + shouldComponentUpdate |
| ロジック再利用 | カスタムフック | HOC / Render props |
| this | なし | コンストラクターでbind |
関数コンポーネントは線形な実行フローを提供します。クラスコンポーネントでは、関連するロジックが複数のライフサイクルメソッドに散らばることがよくあります。例えば、WebSocket購読はcomponentDidMountで設定され、componentWillUnmountで解除されます。関数コンポーネントでは、両方の操作がクリーンアップ関数を返す1つのuseEffectに含まれています。
TypeScriptはクラスよりも関数コンポーネントとより自然に連携します。Propsの型付け — 関数の引数に対するインターフェース。戻り値の型はJSX.ElementまたはReactNodeです。useStateは初期値から自動的に型を推論します。
import React, { useState } from 'react';
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary';
onClick: () => void;
}
const Button: React.FC<ButtonProps> =
({ label, variant = 'primary', onClick }) => (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{label}
</button>
);
現代のTypeScriptでは、判別共用体型でuseReducerを型付けしたり、カスタムフックでジェネリクスを使用したり、React.PropsWithChildrenを通じて子要素の厳密な検証が可能です。TypeScriptを用いた関数コンポーネントは、追加の抽象化なしで完全な型安全性を提供します。
よくある質問
関数コンポーネントはよりシンプルです:コンストラクター、bind、this、散在するライフサイクルメソッドがありません。フックは関連するロジックをグループ化できます。Reactはクラスではなくフックの開発にリソースを投資しています。Server Componentsや新機能は関数でのみ利用可能です。
はい、useEffectを通じて、内部にタイマーとclearInterval用のクリーンアップ関数を配置します。クロージャに注意してください:カウンターを ticks させる場合、setStateの関数形式を使用します:setCount(c => c + 1) これにより、クロージャ内の古い値に依存することを避けられます。
以前の値を保存するにはuseRefを使用します。各レンダリング後にuseEffectでrefを更新します。カスタムフックusePreviousがクリーンな比較に役立ちます:const prev = usePrevious(value);
React.memoはコンポーネント全体をメモ化するHOCです:propsが変わらなければ再レンダリングを防ぎます。useMemoはコンポーネント内の特定の値をメモ化するフックです:依存配列によってレンダリング間で関数の結果をキャッシュします。
最も簡単な方法は、propsからの初期化を行うuseStateと、同期のためのpropsへの依存関係を持つuseEffectを使用することです。稀なケースでは、Reactは入力データが変更されたときにコンポーネントを強制的に再作成するためにkeyを使用することを推奨しています。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。