Back

よくあるJSXの間違いとその回避方法

よくあるJSXの間違いとその回避方法

JSXは一見シンプルに見えます。JavaScriptの中にHTMLを書くだけですよね?しかし、経験豊富な開発者でさえ、その癖につまずくことがあります。特にReactが進化するにつれて、その傾向は顕著です。React 19の自動JSXランタイム、Server Components、そして現代のフレームワークの変化する状況により、これらの間違いは新たな影響を持つようになりました。ここでは、開発者がまだつまずいている点と、これらの落とし穴を回避する方法を紹介します。

重要なポイント

  • 配列のインデックスをキーとして使用すると、reconciliationの問題が発生し、Reactのconcurrent機能が壊れる
  • Server Componentsは、特にブラウザAPIに関して、クライアントコンポーネントとは異なるパターンが必要
  • 自動JSXランタイムは、コードの変換方法を変更し、適切な設定が必要
  • インライン関数と条件付きレンダリングのパターンは、パフォーマンスを静かに低下させる可能性がある

JSXの進化:なぜ古い習慣が新しいコードを壊すのか

React 17で導入された自動JSXランタイムにより、すべてのファイルでReactをインポートする必要がなくなりましたが、新たな混乱も生まれました。JSXの変換方法が変わり、jsx関数がReact.createElementに置き換わります。ビルドツールの設定ミスは、アプリを静かに壊す可能性があります。

Server Componentsでは、リスクはさらに高くなります。クライアント側で完璧に動作するJSXが、windowにアクセスしようとしたり、間違ったコンテキストでフックを使用したりすると、クラッシュします。ルールは単に変わっただけでなく、増えているのです。

現代のReactにおける重大なJSXの落とし穴

1. パフォーマンスを破壊する不安定なキー

// ❌ インデックスをキーとして使用 - reconciliationの問題を引き起こす
items.map((item, index) => <Item key={index} {...item} />)

// ✅ 安定した一意の識別子
items.map(item => <Item key={item.id} {...item} />)

配列のインデックスをキーとして使用することは、依然として最も有害なJSXの間違いの1つです。Reactのconcurrent機能では、不安定なキーは単にちらつきを引き起こすだけでなく、Suspense境界を壊し、コンポーネントツリー全体で不要な再レンダリングをトリガーする可能性があります。

2. オブジェクトの直接レンダリング

// ❌ オブジェクトは有効なReactの子要素ではない
const user = { name: 'Alice', age: 30 };
return <div>{user}</div>;

// ✅ 特定のプロパティをレンダリング
return <div>{user.name}</div>;

このエラーメッセージはReact 15以降変わっていませんが、開発者は依然としてオブジェクトを直接レンダリングしようとします。TypeScriptのJSX推論を使用すれば、tsconfig.jsonが適切に設定されていれば、コンパイル時にこれをキャッチできます。

3. 新しい参照を作成するインライン関数

// ❌ レンダリングごとに新しい関数を作成
<Button onClick={() => handleClick(id)} />

// ✅ useCallbackで安定した参照を作成
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<Button onClick={handleButtonClick} />

Reactのレンダリングパイプラインでは、インライン関数は単にパフォーマンスの問題を引き起こすだけでなく、memoの最適化を壊し、コンポーネントツリー全体でカスケード更新をトリガーする可能性があります。

Server Components:JSXのルールが変わる場所

4. Server Componentsでのクライアント専用コード

// ❌ Server Componentsでクラッシュ
export default function ServerComponent() {
  const width = window.innerWidth; // ReferenceError
  return <div style={{ width }} />;
}

// ✅ clientディレクティブを使用するか、クライアントから渡す
'use client';
import { useState, useEffect } from 'react';

export default function ClientComponent() {
  const [width, setWidth] = useState(0);
  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);
  return <div style={{ width }} />;
}

Server Componentsは、DOM APIが存在しないブラウザの外で実行されます。これは設定の問題ではなく、アーキテクチャの問題です。

5. Suspenseなしの非同期コンポーネント

// ❌ Server Componentで処理されないPromise
async function UserProfile({ id }) {
  const user = await fetchUser(id);
  return <div>{user.name}</div>;
}

// ✅ Suspense境界でラップ
<Suspense fallback={<Loading />}>
  <UserProfile id={userId} />
</Suspense>

React Server Componentsは非同期にできますが、適切なSuspense境界がないと、レンダリングをブロックするか、不可解なエラーでクラッシュします。

現代のJSX設定の落とし穴

6. 一致しないJSXランタイム設定

// ❌ tsconfig.jsonでの古い変換
{
  "compilerOptions": {
    "jsx": "react"  // Reactのインポートが必要
  }
}

// ✅ React 17以降の自動ランタイム
{
  "compilerOptions": {
    "jsx": "react-jsx"  // Reactのインポートは不要
  }
}

自動JSXランタイムは単なる便利機能ではなく、最適なバンドルサイズとServer Componentの互換性のために必要です。ここでの設定ミスは、本番環境でのみ表面化するサイレント障害を引き起こします。

7. 条件付きレンダリングのアンチパターン

// ❌ 何も表示しない代わりに0を返す
{count && <Counter value={count} />}

// ✅ 明示的なブール変換
{Boolean(count) && <Counter  value={count} />}

countが0の場合、JSXは何も表示せず、数字の0をレンダリングします。この間違いは、テキストノードが適切なコンテナを必要とするReact Nativeで特に目立ちます。

予防戦略

ツールを適切に設定する: eslint-plugin-reactを使用してESLintをセットアップし、次のルールを有効にします:

  • react/jsx-key
  • react/jsx-no-bind
  • react/display-name

TypeScriptを使用する: 適切なJSX設定により、TypeScriptはこれらのエラーのほとんどをコンパイル時にキャッチします。strictモードを有効にし、tsconfig.jsonjsxを適切に設定してください。

ランタイムを理解する: コンポーネントがサーバーで実行されるか、クライアントで実行されるかを把握してください。Next.js 14以降は'use client'ディレクティブでこれを明示的にしていますが、メンタルモデルはどこでも適用されます。

結論

2024年のJSXの間違いは、単に構文の問題ではなく、コードがどこでどのように実行されるかを理解することです。自動JSXランタイムは変換モデルを変更しました。Server Componentsは実行モデルを変更しました。Reactのconcurrent機能はパフォーマンスモデルを変更しました。

これらの基本をマスターすれば、単に正しいだけでなく、現代のReactの機能に最適化されたJSXを書くことができます。最高のJSXは目に見えません。それは邪魔にならず、コンポーネントを輝かせます。

よくある質問

配列のインデックスをキーとして使用すると、Reactはどのアイテムが変更、移動、または削除されたかを適切に追跡できません。これにより、Reactは必要以上に多くのコンポーネントを再レンダリングすることを余儀なくされ、並べ替え後に状態が間違ったコンポーネントに関連付けられる可能性があります。

インライン関数は機能的には動作しますが、レンダリングごとに新しい参照を作成し、React.memoの最適化を壊し、子コンポーネントが不必要に再レンダリングされる可能性があります。保守性を向上させるために、propsやstateに依存するイベントハンドラにはuseCallbackを使用してください。

reactの設定は、すべてのファイルでReactのインポートを必要とする従来のReact.createElement変換を使用します。react-jsxの設定は、React 17で導入された自動ランタイムを使用し、明示的なReactのインポートなしで変換を処理し、より小さなバンドルを生成します。

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay