React 19新機能と移行ガイド - 次世代Reactでの開発体験を劇的改善

Tech Trends AI
- 6 minutes read - 1077 wordsReact 19新機能と移行ガイド
React 19は、開発者体験とアプリケーションパフォーマンスを大幅に改善する多くの新機能を導入しました。この記事では、React 19の主要な新機能と、既存のプロジェクトから移行する際の手順について詳しく解説します。
React 19の主要な新機能
1. 新しいReact Compiler
React 19では、自動的にコンポーネントを最適化する新しいコンパイラが導入されました。
// Before: 手動でuseMemo/useCallbackを使用
const MyComponent = ({ items }) => {
const expensiveValue = useMemo(() => {
return items.map(item => processItem(item));
}, [items]);
const handleClick = useCallback(() => {
// 処理
}, []);
return <div>{/* レンダリング */}</div>;
};
// After: コンパイラが自動的に最適化
const MyComponent = ({ items }) => {
const expensiveValue = items.map(item => processItem(item));
const handleClick = () => {
// 処理
};
return <div>{/* レンダリング */}</div>;
};
2. Actions機能
フォームやデータ処理をより簡単に扱えるActions機能が追加されました。
import { useActionState } from 'react';
function ContactForm() {
const [state, submitAction, isPending] = useActionState(
async (previousState, formData) => {
try {
await sendMessage({
name: formData.get('name'),
email: formData.get('email'),
message: formData.get('message')
});
return { success: true, message: 'メッセージを送信しました' };
} catch (error) {
return { success: false, message: 'エラーが発生しました' };
}
},
{ success: false, message: '' }
);
return (
<form action={submitAction}>
<input name="name" placeholder="お名前" required />
<input name="email" type="email" placeholder="メールアドレス" required />
<textarea name="message" placeholder="メッセージ" required />
<button type="submit" disabled={isPending}>
{isPending ? '送信中...' : '送信'}
</button>
{state.message && (
<p style={{ color: state.success ? 'green' : 'red' }}>
{state.message}
</p>
)}
</form>
);
}
3. use Hook
非同期データを扱う新しいuse Hookが追加されました。
import { use, Suspense } from 'react';
function UserProfile({ userId }) {
// Promiseを直接消費
const user = use(fetchUser(userId));
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
function App() {
return (
<Suspense fallback={<div>読み込み中...</div>}>
<UserProfile userId="123" />
</Suspense>
);
}
// 条件付きでHookを使用することも可能
function ConditionalData({ shouldLoad, dataPromise }) {
if (!shouldLoad) {
return <div>データは読み込まれません</div>;
}
const data = use(dataPromise);
return <div>{data.content}</div>;
}
4. Document Metadata
<title>や<meta>タグをコンポーネント内で直接記述できるようになりました。
function BlogPost({ post }) {
return (
<>
<title>{post.title} - マイブログ</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.excerpt} />
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
</>
);
}
5. ref as prop
forwardRefを使わずに、refを直接propsとして渡せるようになりました。
// Before: forwardRefが必要
const MyInput = forwardRef((props, ref) => {
return <input ref={ref} {...props} />;
});
// After: 直接refをpropsとして受け取り可能
function MyInput({ ref, ...props }) {
return <input ref={ref} {...props} />;
}
// 使用方法
function Parent() {
const inputRef = useRef();
return (
<MyInput
ref={inputRef}
placeholder="入力してください"
onFocus={() => console.log('フォーカス')}
/>
);
}
6. Context の改善
Context の最適化により、Provider の不要な再レンダリングが削減されました。
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
// React 19では、この値オブジェクトのメモ化が不要
return (
<ThemeContext value={{ theme, setTheme }}>
<Header />
<MainContent />
<Footer />
</ThemeContext>
);
}
React 19への移行手順
Step 1: 依存関係の更新
# React 19へのアップグレード
npm update react react-dom
# TypeScriptを使用している場合
npm update @types/react @types/react-dom
# その他の関連パッケージの更新
npm update @testing-library/react @testing-library/jest-dom
Step 2: 破壊的変更への対応
Legacy Context の削除
React 19では、Legacy Context APIが削除されました。
// Legacy Context (削除済み)
class LegacyProvider extends Component {
static childContextTypes = {
theme: PropTypes.string
};
getChildContext() {
return { theme: 'dark' };
}
render() {
return this.props.children;
}
}
// Modern Context に移行
const ThemeContext = createContext('light');
function ModernProvider({ children }) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
}
String refs の削除
String refs も完全に削除されました。
// String refs (削除済み)
class OldComponent extends Component {
componentDidMount() {
this.refs.myInput.focus(); // エラー
}
render() {
return <input ref="myInput" />;
}
}
// useRef または createRef に移行
function NewComponent() {
const myInputRef = useRef();
useEffect(() => {
myInputRef.current.focus();
}, []);
return <input ref={myInputRef} />;
}
Step 3: パフォーマンス最適化の見直し
React Compiler により、多くの手動最適化が不要になります。
// Before: 手動最適化が必要
const ExpensiveComponent = memo(({ data, onUpdate }) => {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
formatted: formatData(item)
}));
}, [data]);
const handleUpdate = useCallback((id, newValue) => {
onUpdate(id, newValue);
}, [onUpdate]);
return (
<div>
{processedData.map(item => (
<Item
key={item.id}
item={item}
onUpdate={handleUpdate}
/>
))}
</div>
);
});
// After: コンパイラが自動最適化するため簡素化可能
function ExpensiveComponent({ data, onUpdate }) {
const processedData = data.map(item => ({
...item,
formatted: formatData(item)
}));
const handleUpdate = (id, newValue) => {
onUpdate(id, newValue);
};
return (
<div>
{processedData.map(item => (
<Item
key={item.id}
item={item}
onUpdate={handleUpdate}
/>
))}
</div>
);
}
Step 4: 新機能の段階的導入
Actions の導入
既存のフォームをActions パターンに移行します。
// Before: useState + useEffect パターン
function ContactForm() {
const [formData, setFormData] = useState({});
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
try {
await submitForm(formData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
{/* フォーム内容 */}
</form>
);
}
// After: useActionState を使用
function ContactForm() {
const [state, submitAction, isPending] = useActionState(
async (prevState, formData) => {
try {
await submitForm(Object.fromEntries(formData));
return { success: true, error: '' };
} catch (error) {
return { success: false, error: error.message };
}
},
{ success: false, error: '' }
);
return (
<form action={submitAction}>
{/* フォーム内容 */}
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
</form>
);
}
Step 5: TypeScript設定の更新
React 19 の新機能を TypeScript で使用する場合の設定です。
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"lib": ["dom", "dom.iterable", "es6", "es2020"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true
}
}
// 新しい型定義の活用
interface User {
id: string;
name: string;
email: string;
}
function UserComponent({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
Step 6: テストの更新
React 19 の新機能に対応したテストの書き方です。
// Actions のテスト
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('should handle form submission with Actions', async () => {
const user = userEvent.setup();
render(<ContactForm />);
await user.type(screen.getByPlaceholderText('お名前'), 'テストユーザー');
await user.type(screen.getByPlaceholderText('メールアドレス'), 'test@example.com');
await user.type(screen.getByPlaceholderText('メッセージ'), 'テストメッセージ');
const submitButton = screen.getByRole('button', { name: '送信' });
await user.click(submitButton);
// ローディング状態の確認
expect(screen.getByText('送信中...')).toBeInTheDocument();
// 成功メッセージの確認
await waitFor(() => {
expect(screen.getByText('メッセージを送信しました')).toBeInTheDocument();
});
});
// use Hook のテスト
test('should render data from promise with use hook', async () => {
const mockData = { id: '1', name: 'テストユーザー' };
const promise = Promise.resolve(mockData);
render(
<Suspense fallback={<div>読み込み中...</div>}>
<UserProfile userPromise={promise} />
</Suspense>
);
expect(screen.getByText('読み込み中...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('テストユーザー')).toBeInTheDocument();
});
});
移行時の注意点
1. 段階的な移行戦略
すべての機能を一度に移行せず、段階的にアップグレードすることを推奨します。
// Phase 1: 基本的なアップグレード(依存関係の更新)
// Phase 2: 破壊的変更への対応
// Phase 3: パフォーマンス最適化の見直し
// Phase 4: 新機能の導入
// 例:部分的な新機能導入
function App() {
return (
<div>
{/* 既存のコンポーネント */}
<LegacyComponent />
{/* 新機能を使った新しいコンポーネント */}
<Suspense fallback={<div>読み込み中...</div>}>
<NewComponentWithUseHook />
</Suspense>
</div>
);
}
2. パフォーマンス監視
React 19 への移行後は、パフォーマンスの変化を監視することが重要です。
// React DevTools Profiler を使った監視
function App() {
return (
<Profiler
id="App"
onRender={(id, phase, actualDuration) => {
console.log('Render stats:', { id, phase, actualDuration });
}}
>
<MainApp />
</Profiler>
);
}
// Web Vitals の監視
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);
3. 互換性のチェック
サードパーティライブラリとの互換性を確認します。
// package.json での依存関係チェック
{
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
// 以下のライブラリがReact 19に対応しているか確認
"react-router-dom": "^6.x.x",
"react-query": "^3.x.x",
"material-ui": "^5.x.x"
}
}
まとめ
React 19は開発者体験とアプリケーションパフォーマンスを大幅に改善する重要なアップデートです。新しいコンパイラ、Actions機能、use Hook、そして様々な最適化により、より効率的で保守性の高いReactアプリケーションを構築できます。
移行の際は:
- 段階的にアップグレードし、各ステップでテストを実行
- 破壊的変更に対応し、非推奨APIを最新のものに置き換え
- 新機能を活用して開発体験を向上
- パフォーマンスを監視し、期待される改善を確認
React 19への移行により、開発効率とアプリケーションの品質を大幅に向上させることができます。まずは小さなプロジェクトや新機能から始めて、徐々に全体に適用していくことをお勧めします。