Reactの矢印関数とfunctionの違い|どっちを使うべき?
Reactコンポーネントでの矢印関数とfunction宣言の違いを詳しく解説。使い分けのポイントとパフォーマンス面での注意点を初心者向けに説明します。
みなさん、Reactでコンポーネントを書く時、悩みませんか?
「矢印関数と普通の関数、どっちを使えばいいの?」 「何か違いがあるの?」 「パフォーマンスに影響する?」
こんな疑問を持ったことがありませんか?
実は、多くの初心者が同じように悩んでいます。 どちらも同じように動作しますが、実は細かな違いがあるんです。
この記事では、Reactでの矢印関数とfunction宣言の違いを初心者にもわかりやすく解説します。
最後まで読めば、適切な使い分けができるようになりますよ。
基本的な書き方の違い
まずは、2つの書き方を比べてみましょう。
見た目だけでも、結構違いがありますよね。
function宣言による書き方
普通の関数宣言は、こんな感じです。
// function宣言
function MyComponent() {
return <div>Hello World</div>;
}
シンプルなコンポーネントですね。
もう少し複雑な例も見てみましょう。
// イベントハンドラーを含む例
function MyComponent() {
const handleClick = () => {
console.log('クリックされました');
};
return (
<button onClick={handleClick}>
クリック
</button>
);
}
この例では、コンポーネント自体はfunction
宣言で書いています。
でも、イベントハンドラーのhandleClick
は矢印関数で書いていますね。
矢印関数による書き方
矢印関数だと、こんな感じになります。
// 矢印関数
const MyComponent = () => {
return <div>Hello World</div>;
};
function
キーワードの代わりに=>
(矢印)を使います。
さらに省略もできます。
// 1行で書く省略形
const MyComponent = () => <div>Hello World</div>;
returnを省略して、よりコンパクトに書けますね。
イベントハンドラーを含む例も見てみましょう。
// イベントハンドラーを含む例
const MyComponent = () => {
const handleClick = () => {
console.log('クリックされました');
};
return (
<button onClick={handleClick}>
クリック
</button>
);
};
こちらは全て矢印関数で統一されています。
どちらも動作は同じですが、見た目の印象が違いますよね。
実は重要な違いがあります
見た目以外にも、実は重要な違いがいくつかあります。
一つずつ見ていきましょう。
1. ホイスティング(巻き上げ)の違い
まず、ホイスティングという仕組みの違いがあります。
// function宣言:ホイスティングされる
console.log(myFunction()); // 'Hello' が出力される
function myFunction() {
return 'Hello';
}
function宣言は、コードの実行前に先に読み込まれます。 なので、宣言前でも使えるんです。
でも矢印関数は違います。
// 矢印関数:ホイスティングされない
console.log(myArrowFunction()); // エラーが発生
const myArrowFunction = () => {
return 'Hello';
};
この場合、エラーが発生してしまいます。
矢印関数は、定義された後でしか使えません。
ただし、Reactコンポーネントでは通常この違いは問題になりません。 コンポーネント内で適切に書けば、どちらも問題なく動作します。
2. thisの扱いの違い
次に、this
の扱いが違います。
// function宣言:独自のthisを持つ
function MyComponent() {
this.name = 'Component';
const handleClick = function() {
console.log(this.name); // undefinedになる可能性
};
return <button onClick={handleClick}>クリック</button>;
}
function宣言では、関数ごとに独自のthis
を持ちます。
でも矢印関数は違います。
// 矢印関数:thisを継承する
const MyComponent = () => {
const name = 'Component';
const handleClick = () => {
console.log(name); // 正しく動作
};
return <button onClick={handleClick}>クリック</button>;
};
矢印関数は、周りのスコープからthis
を引き継ぎます。
でも大丈夫です!
関数型コンポーネントでは、this
の違いはほとんど影響しません。
3. 記述の簡潔性
矢印関数の方が、より簡潔に書けます。
// function宣言:比較的冗長
function UserCard({ user }) {
return (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
これを矢印関数で書くと、こうなります。
// 矢印関数:より簡潔
const UserCard = ({ user }) => (
<div>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
returnを省略して、括弧で囲むだけで書けますね。
特に、シンプルなコンポーネントでは矢印関数の方がスッキリします。
React特有の考慮事項
Reactならではの考慮すべき点も見てみましょう。
1. React Developer Toolsでの表示
React Developer Toolsでは、コンポーネント名が重要です。
// function宣言:コンポーネント名が表示される
function UserProfile() {
return <div>Profile</div>;
}
function宣言では、関数名がそのままコンポーネント名になります。
矢印関数でも同じです。
// 矢印関数:適切に名前を付ける
const UserProfile = () => {
return <div>Profile</div>;
};
どちらも「UserProfile」として表示されます。
でも、これは避けてください。
// 無名関数は避ける
const UserProfile = function() {
return <div>Profile</div>;
};
名前を付けることが重要です。
2. デフォルトエクスポートとの組み合わせ
エクスポートの書き方も見てみましょう。
// function宣言のエクスポート
function App() {
return <div>App</div>;
}
export default App;
function宣言では、先に定義してから後でエクスポートします。
矢印関数でも同じです。
// 矢印関数のエクスポート
const App = () => {
return <div>App</div>;
};
export default App;
でも、これは避けることをおすすめします。
// インライン(非推奨)
export default () => {
return <div>App</div>;
};
インラインでのエクスポートは避ける方が良いです。
React Developer Toolsでコンポーネント名がわからなくなってしまいます。
パフォーマンス面での違い
「どっちの方が速いの?」と気になりますよね。
実は、パフォーマンス面での違いはほとんどありません。
1. 関数の作成タイミング
どちらも、再レンダリングのたびに関数が作成されます。
// function宣言:再レンダリングのたびに関数が作成される
function MyComponent() {
function handleClick() {
console.log('クリック');
}
return <button onClick={handleClick}>クリック</button>;
}
矢印関数も同じです。
// 矢印関数:再レンダリングのたびに関数が作成される
const MyComponent = () => {
const handleClick = () => {
console.log('クリック');
};
return <button onClick={handleClick}>クリック</button>;
};
どちらも、パフォーマンス面では同じです。
2. useCallbackとの組み合わせ
パフォーマンスを最適化したい場合は、useCallback
を使います。
// useCallbackを使った最適化
const MyComponent = () => {
const handleClick = useCallback(() => {
console.log('クリック');
}, []);
return <button onClick={handleClick}>クリック</button>;
};
useCallback
を使うと、関数が再作成されるのを防げます。
function宣言でも同じように使えます。
// function宣言でも同様
function MyComponent() {
const handleClick = useCallback(() => {
console.log('クリック');
}, []);
return <button onClick={handleClick}>クリック</button>;
}
どちらを使っても、最適化の効果は同じです。
実際の使い分けのガイドライン
実際の開発では、どう使い分けるのが良いでしょうか?
具体的な例で見てみましょう。
1. コンポーネント定義では矢印関数がおすすめ
シンプルなコンポーネントでは、矢印関数がおすすめです。
// 推奨:矢印関数でスッキリと
const Header = () => (
<header>
<h1>サイトタイトル</h1>
</header>
);
1行で書けて、とても読みやすいですね。
複雑な処理がある場合は、function宣言でも良いです。
// 複雑な処理がある場合
function UserDashboard({ user }) {
const [loading, setLoading] = useState(false);
const [data, setData] = useState(null);
useEffect(() => {
fetchUserData();
}, []);
const fetchUserData = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${user.id}`);
const userData = await response.json();
setData(userData);
} finally {
setLoading(false);
}
};
return (
<div>
{loading ? <p>読み込み中...</p> : <UserData data={data} />}
</div>
);
}
この例では、function宣言を使っています。
複雑な処理がある場合は、どちらを使っても問題ありません。
2. イベントハンドラーは矢印関数で統一
イベントハンドラーは、矢印関数で統一することをおすすめします。
// 推奨:矢印関数で統一
const ContactForm = () => {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log('フォーム送信', { email, message });
};
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={handleEmailChange}
/>
<textarea
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="submit">送信</button>
</form>
);
};
全てのイベントハンドラーを矢印関数で統一しています。
インライン(その場で書く)でも使えます。
3. カスタムフックでも矢印関数
カスタムフックでも、矢印関数で統一することをおすすめします。
// 推奨:矢印関数で統一
const useCounter = (initialValue = 0) => {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
};
カスタムフック内の関数も、全て矢印関数で書いています。
使用例も見てみましょう。
// 使用例
const Counter = () => {
const { count, increment, decrement, reset } = useCounter(0);
return (
<div>
<p>カウント: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>リセット</button>
</div>
);
};
全て矢印関数で統一されていて、読みやすいですね。
現在のベストプラクティス
最後に、現在のベストプラクティスを紹介します。
1. 一貫性を保つことが最重要
プロジェクト全体で一貫したスタイルを使用することが重要です。
// プロジェクト全体で一貫したスタイルを使用
const App = () => {
return (
<div>
<Header />
<Main />
<Footer />
</div>
);
};
const Header = () => <header>ヘッダー</header>;
const Main = () => <main>メイン</main>;
const Footer = () => <footer>フッター</footer>;
全てのコンポーネントで矢印関数を使っています。
チーム内で統一されていれば、どちらを使っても問題ありません。
2. ESLintでスタイルを統一
ESLintを使って、スタイルを自動的に統一できます。
// .eslintrc.json
{
"rules": {
"prefer-arrow-callback": "error",
"func-style": ["error", "expression", { "allowArrowFunctions": true }]
}
}
これらのルールを設定すると、矢印関数を優先的に使うようになります。
3. TypeScriptでの型定義
TypeScriptを使う場合の型定義も見てみましょう。
// TypeScriptでの型定義
type Props = {
title: string;
children: React.ReactNode;
};
const Card: React.FC<Props> = ({ title, children }) => (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
矢印関数でも、きちんと型定義できます。
function宣言でも同じです。
// function宣言でも同様
function Card({ title, children }: Props) {
return (
<div className="card">
<h2>{title}</h2>
{children}
</div>
);
}
どちらを使っても、型の恩恵は同じように受けられます。
よくある質問
初心者の方からよく聞かれる質問にお答えします。
Q1: パフォーマンスに違いはある?
A1: 関数型コンポーネントでは、パフォーマンスの違いはほとんどありません。
どちらも同じように最適化されます。
心配する必要はありませんよ。
Q2: どちらが読みやすい?
A2: 矢印関数の方が簡潔で読みやすいとされることが多いです。
でも、これは個人の好みやチームの方針によります。
最も重要なのは、チーム内で統一されていることです。
Q3: 古いブラウザでの対応は?
A3: Babelでトランスパイルされるため、古いブラウザでも問題ありません。
モダンなReactアプリケーションでは、どちらを使っても大丈夫です。
安心して使ってください。
Q4: 初心者はどっちを使うべき?
A4: 矢印関数から始めることをおすすめします。
現在のReactコミュニティでは、矢印関数が主流になっています。
チュートリアルや記事も、矢印関数を使ったものが多いです。
Q5: 混在させても問題ない?
A5: 技術的には問題ありませんが、統一することをおすすめします。
コードの可読性と保守性を考えると、どちらか一方に統一した方が良いでしょう。
まとめ
お疲れさまでした!
ReactでのArrow Function vs Function宣言について、詳しく見てきました。
重要なポイントをまとめると、以下の通りです:
技術的な違い
- ホイスティングの有無
- thisの扱い方
- 記述の簡潔性
実用的な観点
- パフォーマンスの違いはほとんどない
- どちらも正しく動作する
- 一貫性が最も重要
現在のトレンド
- 矢印関数が主流になっている
- ESLintで統一できる
- TypeScriptでもどちらも使える
おすすめの使い分け
- コンポーネント定義:矢印関数
- イベントハンドラー:矢印関数
- カスタムフック:矢印関数
- 複雑な処理:どちらでも可
最終的には、プロジェクト内での一貫性が最も重要です。
チームで決めたルールに従って、読みやすく保守しやすいコードを書いてください。
どちらを選んでも、適切に書かれたReactコンポーネントは正常に動作します。
ぜひ今回の知識を活用して、自分に合ったスタイルを見つけてくださいね。
まずは矢印関数から始めて、慣れてきたらチームのルールに合わせて調整していきましょう!